I've been struggling with this one since yesterday - I'm creating new ScopedModel obj in tab controller (ScreenTabs), and then passing it to multiple tabs in BottomNavigationBar (1st level descendants, for example: ScreenBook) by wrapping each tab as ScopedModelDescendant. So far so good - everything's working as expected.
The problem arises, when in one of my tabs, I'd like to navigate to it's child (2nd level descendant, for example: ScreenBookDetails) and use ScopedModel there. I've already tried using ScopedModelDescendant and ScopedModel.of(context) with no luck - getting Error: Could not find the correct ScopedModel everytime.
My code (skipped some for brevity):
TabController
...
class ScreenTabsState extends State<ScreenTabs> {
ModelTabs modelTabs = ModelTabs(); //new Model object
var screens = [new ScreenHome(),new ScreenBook(),];
#override
Widget build(BuildContext context) {
return ScopedModel<ModelTabs>(
model: modelTabs,
child: Scaffold(...)
body: Builder (builder: (context) => Container(child:
screens.elementAt(selectedIndex),)),
bottomNavigationBar: BottomNavigationBar(
currentIndex: selectedIndex,
items: [...],
onTap: onTabTapped,
),)...}
ScreenBook (1st ddescendant - ScpodedModel works as expected)
...
class ScreenBookState extends State<ScreenBook> {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: ScopedModelDescendant<ModelTabs>(builder: (context, child, model) {
print(TAG + "model status:" + model.status.toString());
return model.status == Status.LOADING ? MyCircularProgressIndicator() :
Builder (builder: (context) =>
Card(...
child: InkWell(
onTap: (){
Navigator.of(context).pushNamed("/ScreenBookDetails");},))}}
ScreenBookDetails (1st ddescendant - error)
...
class ScreenBookDetailsState extends State<ScreenBookDetails>{
#override
Widget build(BuildContext context) {
return Scaffold(
body: ScopedModelDescendant<ModelTabs>(builder: (context, child, model) {
print(TAG + "scoped model status: " + model.status.toString()); //Error: Could not find the correct ScopedModel.
return model.status == Status.LOADING ? MyCircularProgressIndicator() : Builder(builder: (context) => Column(...)
...
can somebody point me the right way to fix thiis problem please? I'm new to Dart and Flutter, so there might be something that I did not understood clearly as I'm facing similar problem when creating ScopedModel obj in ScreenSplashScreen then navigating to ScreenLogIn and trying to use it there.
Thank you! :)
Finally found out how to handle ScopedModel including Navigator with help from ScopedModel's Github support - the key was to create a ScopeModel instance outside of build(BuildContext), then pass the instance through the constructor of next screen, like:
class HomePage extends StatelessWidget {
SampleScopedModel model = SampleScopedModel();
#override
Widget build(BuildContext context) {
return ScopedModel<SampleScopedModel>(
model: model,
child: Scaffold(
body: Container(),
floatingActionButton: FloatingActionButton(onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => NextPage(model))
);
}),
),
);
}
}
class NextPage extends StatelessWidget {
final SampleScopedModel model;
NextPage(this.model);
#override
Widget build(BuildContext context) {
return ScopedModel<SampleScopedModel>(
model: model,
child: Scaffold(
body: ScopedModelDescendant<SampleScopedModel>(
builder: (context, child, model) {
return Text(model.payload.toString());
})
),
);
}
}
I'm using the Flutter login home animation from GeekyAnts. You can find it on:
https://github.com/GeekyAnts/flutter-login-home-animation
I can't get to handle username and password values entered in the fields as these fields are being called from the login page through a Widget called FormContainer which contains two other Widgets called InputFieldArea.
When using onChanged in the TextFormField, I don't know how to make these values reach the parent LoginPage class.
Could you help me understand how changes in fields in the Login page should be handled in order to make the login work?
Thanks!
Use a TextEditingController, as described in Retrieve the value of a text field.
TextFormField takes the controller as a constructor argument, you can pass it down to your InputFieldArea through a similar constructor:
class InputFieldArea extends StatelessWidget {
final TextEditingController controller;
final bool obscureText;
// ...
const InputFieldArea({Key key, this.controller, this.obscureText}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.orange,
// ...
child: TextFormField(
controller: controller,
obscureText: obscureText,
// ...
),
);
}
}
It's important that the controllers are stored in a State, so that if the widgets are rebuilt for some reason, the controllers are not recreated:
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
InputFieldArea(
controller: _usernameController,
obscureText: false,
),
InputFieldArea(
controller: _passwordController,
obscureText: true,
),
RaisedButton(
onPressed: () {
// example how to read the current text field values
print('username: ${_usernameController.text}, password: ${_passwordController.text}');
},
)
],
)
);
}
}
Also check out the shrine login demo.
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),
],
),),
);
}
}
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
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"),
),
),
);
}
}
We can access it easily just like below.
className().MethodName(),
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()));
}
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.
We can take help instance Make instance given below
var objectName = new ClassName(<constructor_arguments>)
Note: We can use an empty constructor like this example.
class Student{
void female(){
print('This is female method');
}
void male(){
print('This is malemethod'); }
}
step1: var _instance1 = new Student(); here empty constructor it dos't matter.
step2: _instance1.male(); Call method _instance1 what we want.
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'),
),
);
}
}
I'm currently working through learning Flutter. I'm starting with trying to build the basic list app. The current flow I have is my Stateful TasksManager Widget saving user input to the state, and then pushing it to the _taskList List, which I have being sent over to a Stateless TaskList widget, which is rendering the list.
I expect the list view to be updated with the new task after the "Save" button is clicked, but what I'm getting is after I "Save", the list view only updates during the subsequent change of state. For example, if I were to "Save" the string "Foo" to add to the list, I'm only seeing that update in the view after I go to type in another item, such as "Bar".
TaskManager
class TasksManager extends StatefulWidget{
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return TasksManagerState();
}
}
class TasksManagerState extends State<TasksManager>{
final List _taskList = [];
String _newTask = '';
#override
Widget build(BuildContext context) {
// TODO: implement build
return Column(
children: <Widget>[
TextField(
onChanged: (value){
setState(() {
_newTask = value;
});
print(_taskList);
},
),
RaisedButton(
child: Text('Save'),
onPressed: () => _taskList.add(_newTask),
),
Expanded(child: TaskList(_taskList))
],
);
}
}
TaskList
class TaskList extends StatelessWidget {
final List taskList;
TaskList(this.taskList);
Widget _buildListItem(BuildContext context, int index) {
return ListTile(
title: Text(taskList[index]),
);
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return ListView.builder(
itemBuilder: _buildListItem,
itemCount: taskList.length,
);
}
}
Yes, Flutter only updates when it is instructed to.
setState() marks the widget dirty and causes Flutter to rebuild:
onPressed: () => setState(() => _taskList.add(_newTask)),
I want to create an App with Tabs to get the users input. The Problem is, that the different Tabs get different inputs, but i have to collect the inputs for the Database. My idea her was, that the main scaffold collects the inputs from all Tabs and write it in a database! My problem is that I don't know to send data from the tab (statefullWidget in an other file) to the parent class (Scaffold) or run a function from there!
Please help me and sorry for my bad English!
Jonas
You can pass a Function that can be called whenever you want.
Small example
MamaBear class
...
class _MamaBear extends State<MamaBear> {
void hungryBear(String babyBear) {
print("$babyBear is hungry");
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(children: <Widget>[
BabyBear(
"Mark",
(babyBear) {
hungryBear(babyBear);
},
)])));}
BabyBear class
class BabyBear extends StatefulWidget {
final String babyBearName;
final Function onBearAction;
BabyBear(this.babyBearName, this.onBearAction);
#override
_BabyBear createState() => _BabyBear();
}
class _BabyBear extends State<BabyBear> {
#override
Widget build(BuildContext context) {
return Card(
child: RaisedButton(
child: Text("Mama I'm hungry"),
onPressed: () {
widget.onBearAction(widget.babyBearName);
}),
);
}
}