Yash
Yash

Reputation: 5968

How to call method from another class in Flutter(Dart)?

I have created an Homepage and from that user can sign in for the app and in the next screen user can see their profile info(Only profile name) and under that their is signOut button. User can signOut from the app using signOut button.But it's not working for me.

I want to call signOut method from main.dart by pressing signOut button in details.dart(both the classes are in different file)

But when i press signOut Button in details.dart nothing happens!

And code is given below:

main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'details.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  MyHomePageState createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
  final  GoogleSignIn googleSignIn = GoogleSignIn();
  static bool _LoginButton = true;

  void signOut(){
    googleSignIn.signOut();
    setState((){
      _LoginButton = true;
    });
    print(_LoginButton);
    print("User Signed Out");
  }

  Future<FirebaseUser> _signIn() async{
    if(_LoginButton==true){
      setState((){
        _LoginButton=false;
      });
      GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn();
      GoogleSignInAuthentication googleSignInAuthentication = await googleSignInAccount.authentication;
      FirebaseUser firebaseUser = await firebaseAuth.signInWithGoogle(idToken: googleSignInAuthentication.idToken, accessToken: googleSignInAuthentication.accessToken);
      print("Username is "+firebaseUser.displayName);
      setState((){
        _LoginButton = true;
      });
      Navigator.push(context, MaterialPageRoute(builder: (context) => details(firebaseUser.displayName,signOut)));

      return firebaseUser;
    }
  }

  bool _LoginButtonBool(){
    return _LoginButton;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Google auth with firebase"),),
      body: Center(
        child: _LoginButtonBool()?Container(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              MaterialButton(onPressed: _LoginButtonBool() ? () => _signIn().then((FirebaseUser firebaseuser ) =>print(firebaseuser)).catchError((e) => print(e)): null,
              child: Text("Login"),color: Colors.orange,),
            ],
          ),
        ):CircularProgressIndicator(backgroundColor: Colors.greenAccent.withOpacity(0.01),),
      ),
    );
  }
}

details.dart

import 'package:flutter/material.dart';
import 'package:flutter_auth/main.dart';

class details extends StatelessWidget {
  String name;
  final Function callback;
  details(this.name,this.callback);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body:Center(child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          Text(name),
          MaterialButton(onPressed: () => callback,
          child: Text("Log out"),color: Colors.orange),
        ],
      ),),
    );
  }
}

Upvotes: 30

Views: 151358

Answers (6)

Baker
Baker

Reputation: 27990

A global key given to a StatefulWidget can give us access to its methods from anywhere.

GlobalKey example

WidgetA below has a method login().

To access login() from other widgets, instantiate WidgetA with a GlobalKey.

Then use that key to access WidgetA state to call login().

The structure below is:

ExamplePage
  WidgetA
    LoginDialog

LoginDialog will call WidgetA.login() using the global key.

login() will update the AppBar in WidgetA with the user name.

WidgetA

Here is the StatefulWidget WidgetA:

class WidgetA extends StatefulWidget {
  const WidgetA({Key? key}) : super(key: key);

  @override
  State<WidgetA> createState() => WidgetAState();

  static GlobalKey<WidgetAState> createKey() => GlobalKey<WidgetAState>();
}


class WidgetAState extends State<WidgetA> {
  String loginStatus = 'Signed Out';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('$loginStatus'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Login'),
          onPressed: () =>
              showDialog(context: context,
                  builder: (context) => LoginDialog()),
        ),
      ),
    );
  }

  void login(String msg) {
    setState(() {
      loginStatus = msg;
    });
  }
}

Notes

  • static method createKey() is a convenience method to create the type of global key we need, a GlobalKey with a Type of WidgetAState
  • when we instantiate WidgetA, we'll need to give it a key of type GlobalKey<WidgetAState>

GlobalKey<WidgetAState>

Here's the global key we'll create of type WidgetAState, using the convenience static method createKey() we added to WidgetA class.

final widgetA = WidgetA.createKey();

To make accessing WidgetAState cleaner, we can create an optional extension class on GlobalKey<WidgetAState> types that gives us direct access to WidgetAState:

extension WidgetAKeyExt on GlobalKey<WidgetAState> {
  void login(String user) => currentState?.login(user);
}

This allows us to make this call:

widgetA.login()

instead of this:

widgetA.currentState?.login()

LoginDialog

This login dialog will access WidgetA's login() method using the global key.

class LoginDialog extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('Login'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          ElevatedButton(
            child: Text('Login'),
            onPressed: () => widgetA.login('Billy'), // WidgetA method call
          ),
          ElevatedButton(
            child: Text('Logout'),
            onPressed: () => widgetA.login('Signed Out'), // WidgetA method call
          ),
        ],
      ),
    );
  }
}

Entire Code Sample

Here's the entire code sample in a single block to copy/paste. Also includes another page WidgetB which also accesses WidgetA state through the global key.

When a login is perfomed in the LoginDialog, the app bar in WidgetA will reflect the loginStatus state. That state is also accessible in WidgetB using the global key.

import 'package:flutter/material.dart';

/// Share access to a widget's methods & state by using a GlobalKey.
/// Create a GlobalKey<T> using that widget's state class as T.
/// For example below we use GlobalKey<WidgetAState>.
///
/// Give that key to WidgetA's constructor / instantiation.
///
/// Then use that key from anywhere to call methods on WidgetAState.

/// Create the key for [WidgetAState] globally so it can be accessed across your app.
/// i.e. do this outside of a Widget, such as in your main.dart or in a state
/// management solution that you access globally, etc.
///
/// For readability/ease we've created a static method [createKey] on [WidgetA] to
/// instantiate the global key we need.
///
/// This could also just be `final widgetA = GlobalKey<WidgetAState>();
final widgetA = WidgetA.createKey(); // see static method inside WidgetA

/// Just an empty page/route to hold [WidgetA] instantiation with global key.
class ExampleGlobalKeyPage extends StatelessWidget {
  const ExampleGlobalKeyPage();

  @override
  Widget build(BuildContext context) {
    return WidgetA(key: widgetA);
  }
}

/// [WidgetA] takes a key argument. We'll give it the global key we created so
/// we can access its state object from anywhere.
class WidgetA extends StatefulWidget {
  const WidgetA({Key? key}) : super(key: key);

  @override
  State<WidgetA> createState() => WidgetAState();

  /// Convenience static method to create the [WidgetAState] key to provide to [WidgetA].
  /// By being specific with the [Key] class as "GlobalKey<WidgetAState>" instead
  /// of just "Key", we can use create & use an extension class [WidgetAKeyExt]
  /// to make accessing the widget state cleaner & easier.  See that extension
  /// class below.
  static GlobalKey<WidgetAState> createKey() => GlobalKey<WidgetAState>();
}

/// This is [WidgetA]'s state object.  By default it's private, but we'll
/// make it public by removing '_' from the name, so [_WidgetAState]
/// becomes [WidgetAState].
///
/// Its [login] method will be available anywhere using the global key we gave
/// [WidgetA] constructor.
///
class WidgetAState extends State<WidgetA> {
  String loginStatus = 'Signed Out'; // this state can be changed with the GlobalKey

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('$loginStatus'),
        actions: [
          IconButton(icon: Icon(Icons.arrow_circle_right_rounded), onPressed: gotoB,)
        ],
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Login'),
          onPressed: () => showDialog(context: context,
          builder: (context) => LoginDialog()),
        ),
      ),
    );
  }

  /// This method can be called from [LoginDialog] widget using the GlobalKey
  void login(String msg) {
    setState(() {
      loginStatus = msg;
    });
  }

  void gotoB() => Navigator.push(context, MaterialPageRoute(builder: (c) => WidgetB()));
}

/// This extension class is not needed, but makes access to the [login] method
/// a little cleaner.
///
/// This extension method on GlobalKey<WidgetAState> performs the null check
/// for [currentState] existence before calling [login],
/// wherever/whenever we use [widgetA] key.
///
/// Also it uses "currentState" for us.
/// So our call `widgetA.currentState?.login()` is now `widgetA.login()`.
///
extension WidgetAKeyExt on GlobalKey<WidgetAState> {
  void login(String user) => currentState?.login(user);

  String get loginStatus => currentState?.loginStatus ?? 'Signed Out';
}


/// This Dialog is a separate route & widget. It makes calls to [WidgetA] methods
/// using the global key [widgetA].
class LoginDialog extends StatelessWidget {
  /// For visibility you may want to pass the global key as a constructor arg
  /// rather than using it directly.  If so, you'd do something like:
  //final GlobalKey<WidgetAState> widgetA;
  //const LoginDialog({super.key, required this.widgetA});

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('Login'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          ElevatedButton(
            child: Text('Login'),
            onPressed: () => widgetA.login('Billy'), // WidgetA method call
          ),
          ElevatedButton(
            child: Text('Logout'),
            onPressed: () => widgetA.login('Signed Out'), // WidgetA method call
          ),
        ],
      ),
    );
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('${widgetA.loginStatus}'),
      ),
      body: Center(
        child: Text('Some other page accessing WidgetA'),
      ),
    );
  }
}

Upvotes: -1

Osama Buzdar
Osama Buzdar

Reputation: 1222

It is simple let me explain with an example

class Animals
{
  var animalList = ['dog','cat','cow'];

  // function for printing the list of animals

  void animalListPrinter(){
    for(var animal in animalList){
        print(animal);

     }
  }
}

Calling the above function to another class

class ShowingAnimalList extends StatelessWidget {
     final Animals ani= new Animals();
     @override
     Widget build(BuildContext context) {
       return GestureDetector(
         onTap:()=> ani.animalListPrinter(),
       );
  }
}

You can call any Widget with this from the parent class

Upvotes: 27

Gayan Chinthaka
Gayan Chinthaka

Reputation: 569

We can access it easily just like below.

className().MethodName(),

Upvotes: 11

Tushar
Tushar

Reputation: 35

you can create another logout() function and give context of home to push back to sign in screen/home screen , works for me as :

      logout() async {
        await googleSignIn.signOut();
        Navigator.push(context, MaterialPageRoute(builder: (context) => Home()));
      }

Upvotes: 2

chemamolins
chemamolins

Reputation: 20548

You must be careful with what you are trying to do because you might be accessing a page/widget that is not mounted. Imagine you do a pushReplacement(new MaterialPageroute(...)). The previous page is no longer available in the tree so you can't access it nor any of its methods.

Unless you have a clear parent child relationship in your tree, you should abstract away your logic to external or business logic classes. Thus you are sure that you are calling active instances of your classes.

Here is an example of what you could use passing around the Business object. It would be even better if you use other patterns like BLOC, ScopedModel, Streams, etc. But for the sake of simplicity I think this should be enough.

import "package:flutter/material.dart";

void main() {
  runApp(MyApp(new Logic()));
}

class Logic {
  void doSomething() {
    print("doing something");
  }
}

class MyApp extends StatelessWidget {
  final Logic logic;

  MyApp(this.logic);

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new HomePage(widget.logic),
    );
  }
}

class HomePage extends StatelessWidget {
  final Logic logic;

  HomePage(this.logic);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: FlatButton(
          onPressed: () { Navigator.of(context).pushReplacement(
             MaterialPageRoute(
               builder: (context) => AnotherPage(logic),
             ))},
          child: Text("Go to AnotherPage"),
        ),
      ),
    );
  }
}

class AnotherPage extends StatelessWidget {
  final Logic logic;

  AnotherPage(this.logic);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: FlatButton(
          onPressed: logic.doSomething,
          child: Text("Press me"),
        ),
      ),
    );
  }
}

If you still want to call a function in the other Page and you are sure the page is mounted (you have done a push instead of a pushReplacement) you could do the following. (handle with care)

class HomePage extends StatelessWidget {

  HomePage();

  void onCalledFromOutside() {
    print("Call from outside");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
    child: FlatButton(
          onPressed: () { Navigator.of(context).push(
             MaterialPageRoute(
               builder: (context) => AnotherPage(onCalledFromOutside),
             ))},
          child: Text("Go to AnotherPage"),
        ),
      ),
    );
  }
}

class AnotherPage extends StatelessWidget {
  final Function callback

  AnotherPage(this.callback);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
    child: FlatButton(
           onPressed: callback,
           child: Text("Press me"),
        ),
      ),
    );
  }
}

Upvotes: 17

Alex Lindroos
Alex Lindroos

Reputation: 19

Import HomePage class in DetailsPage and make a new instance out of it, then call the method you want if it's a public one.

Upvotes: 0

Related Questions