Pass parameter to initState - dart

Look at this code - widget to fetch data and display on list:
class _MyEventsFragmentState extends State <MyEventsFragment>{
var events;
#override
initState(){
super.initState();
events = fetchEvents(true);
}
#override
Widget build(BuildContext context) {
return new Center(
child: FutureBuilder<EventsResponse>(
future: events,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
helpers.logout(context, Strings.msg_session_expired);
return CircularProgressIndicator();
}
return new Container(color: Colors.white,
child: new ListControl().build(snapshot));
}
return CircularProgressIndicator();
},
)
);
}
}
fetchEvent method has parameter to indicate which events I need to fetch. If set to true, - my events, if set to false - all events returned. Above code loads my events and fetchEvents is called inside initState override to avoid unnecesary data reloading.
To fetch all events I defined another class:
class EventsFragment extends StatefulWidget {
#override
_EventsFragmentState createState() => new _EventsFragmentState();
}
class _EventsFragmentState extends State <EventsFragment>{
var events;
#override
initState(){
super.initState();
events = fetchEvents(false);
}
#override
Widget build(BuildContext context) {
return new Center(
child: FutureBuilder<EventsResponse>(
future: events,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
helpers.logout(context, Strings.msg_session_expired);
return CircularProgressIndicator();
}
return new Container(color: Colors.white,
child: new ListControl().build(snapshot));
}
return CircularProgressIndicator();
},
)
);
}
}
But this is very dumb solution, because code is almost the same. So I tried to pass boolean value to indicate which events to load, something like that:
#override
initState(){
super.initState();
events = fetchEvents(isMyEvents);
}
isMyEvents should be got from EventsFragment constructor. However, it won't be accesible inside initState. Ho to pass it properly? I could access it inside build override, but not inside initState. How to pass it properly and make sure it will be refreshed every time widget instance is created?
[edit]
So this how I solved my problem (it seems to be fine):
class EventsFragment extends StatefulWidget {
const EventsFragment({Key key, this.isMyEvent}) : super(key: key);
final bool isMyEvent;
#override
_EventsFragmentState createState() => new _EventsFragmentState();
}
class _EventsFragmentState extends State <EventsFragment>{
var events;
#override
initState(){
super.initState();
events = fetchEvents(widget.isMyEvent);
}
#override
void didUpdateWidget(EventsFragment oldWidget) {
if(oldWidget.isMyEvent != widget.isMyEvent)
events = fetchEvents(widget.isMyEvent);
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
return new Center(
child: FutureBuilder<EventsResponse>(
future: events,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
helpers.logout(context, Strings.msg_session_expired);
return CircularProgressIndicator();
}
return new Container(color: Colors.white,
child: new ListControl().build(snapshot));
}
return CircularProgressIndicator();
},
)
);
}
}

Pass such parameter to the StatefulWidget subclass, and use that field instead
class Foo extends StatefulWidget {
const Foo({Key key, this.isMyEvent}) : super(key: key);
final bool isMyEvent;
#override
_FooState createState() => _FooState();
}
class _FooState extends State<Foo> {
#override
void initState() {
super.initState();
print(widget.isMyEvent);
}
#override
Widget build(BuildContext context) {
return Container(
);
}
}

Related

Flutter - RepaintBoundary causes state reset of StatefulWidget

I have a preview widget that loads data after a user tap. This state (already tapped or not) should not be lost while scrolling (the preview is located in a list) or navigating through other screen.
The scrolling is solved by adding AutomaticKeepAliveClientMixin which saves the state when scrolling away.
Now i also need to wrap the preview widget (actually a more complex widget that contains the preview) with a RepaintBoundary, to be able to make a "screenshot" of this widget alone.
Before i wrap the widget with a RepaintBoundary, the state is saved both while scrolling and navigating to another screen.
After i add the RepaintBoundary the scrolling still works but for navigation the state is reset.
How can i wrap a Stateful widget that should hold its state with a RepaintBoundary?
Code is a simplified example of my implementation with the same problem.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final title = 'Test';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: TestList(40),
),
);
}
}
class TestList extends StatefulWidget {
final int numberOfItems;
TestList(this.numberOfItems);
#override
_TestListState createState() => _TestListState();
}
class _TestListState extends State<TestList> {
#override
Widget build(BuildContext context) {
print('_TestListState build.');
return ListView.builder(
itemCount: widget.numberOfItems,
itemBuilder: (context, index) {
return RepaintBoundary(
key: GlobalKey(),
child: Preview()
);
},
);
}
}
class Preview extends StatefulWidget {
#override
_PreviewState createState() => _PreviewState();
}
class _PreviewState extends State<Preview> with AutomaticKeepAliveClientMixin {
bool loaded;
#override
void initState() {
super.initState();
print('_PreviewState initState.');
loaded = false;
}
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
print('_PreviewState build.');
if(loaded) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NewScreen()),
);
},
child: ListTile(
title: Text('Loaded. Tap to navigate.'),
leading: Icon(Icons.visibility),
),
);
} else {
return GestureDetector(
onTap: () {
setState(() {
loaded = true;
});
},
child: ListTile(
title: Text('Tap to load.'),
),
);
}
}
}
class NewScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('New Screen')),
body: Center(
child: Text(
'Navigate back and see if loaded state is gone.',
style: TextStyle(fontSize: 14.0),
),
),
);
}
}
Take a look at RepaintBoundary.wrap, it assigns the RepaintBoundary widget a key based on its child or childIndex so state is maintained:
class _TestListState extends State<TestList> {
#override
Widget build(BuildContext context) {
print('_TestListState build.');
return ListView.builder(
itemCount: widget.numberOfItems,
itemBuilder: (context, index) {
return RepaintBoundary.wrap(
Preview(),
index,
);
},
);
}
}
https://api.flutter.dev/flutter/widgets/RepaintBoundary/RepaintBoundary.wrap.html
EDIT: As per the below comments, it looks like this solution would break the screenshot ability so you'd have to store the list of children widgets in your state like so:
class _TestListState extends State<TestList> {
List<Widget> _children;
#override
void initState() {
super.initState();
_children = List.generate(
widget.numberOfItems,
(_) => RepaintBoundary(
key: GlobalKey(),
child: Preview(),
));
}
#override
Widget build(BuildContext context) {
print('_TestListState build.');
return ListView(children: _children);
}
}

How to maintain Flutter Global BloC state using Provider on Hot Reload?

I seem to lose application state whenever I perform a hot reload.
I am using a BloC provider to store application state. This is passed at the App level in the main.dart and consumed on a child page. On the initial load of the view, the value is shown. I can navigate around the application and the state persists. However, when I perform a hot reload, I lose the values and seemingly the state.
How can I fix this issue so that state is preserved on Hot Reload?
Bloc Provider
abstract class BlocBase {
void dispose();
}
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider({
Key key,
#required this.child,
#required this.bloc,
}): super(key: key);
final T bloc;
final Widget child;
#override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context){
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
return provider.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{
#override
void dispose(){
widget.bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context){
return widget.child;
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return BlocProvider<ApplicationStateBloc>(
bloc: ApplicationStateBloc(),
child: MaterialApp(
title: 'Handshake',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: LoadingPage(),
)
);
}
}
class ProfileSettings extends StatefulWidget {
#override
_ProfileSettingsState createState() => _ProfileSettingsState();
}
class _ProfileSettingsState extends State<ProfileSettings>{
ApplicationStateBloc _applicationStateBloc;
#override
void initState() {
super.initState();
_applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(context);
}
#override
void dispose() {
_applicationStateBloc?.dispose();
super.dispose();
}
Widget emailField() {
return StreamBuilder<UserAccount>(
stream: _applicationStateBloc.getUserAccount,
builder: (context, snapshot){
if (snapshot.hasData) {
return Text(snapshot.data.displayName, style: TextStyle(color: Color(0xFF151515), fontSize: 16.0),);
}
return Text('');
},
);
}
#override
Widget build(BuildContext context) {
return BlocProvider<ApplicationStateBloc>(
bloc: _applicationStateBloc,
child: Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: <Widget>[
emailField(),
.... // rest of code
class ApplicationStateBloc extends BlocBase {
var userAccountController = BehaviorSubject<UserAccount>();
Function(UserAccount) get updateUserAccount => userAccountController.sink.add;
Stream<UserAccount> get getUserAccount => userAccountController.stream;
#override
dispose() {
userAccountController.close();
}
}
I was facing the same problem. Inherited widgets make it hard disposing bloc's resources.
Stateful widget, on the other hand, allows disposing, but in the implementation you're using it doesn't persist the bloc in the state causing state loss on widgets rebuild.
After some experimenting I came up with an approach that combines the two:
class BlocHolder<T extends BlocBase> extends StatefulWidget {
final Widget child;
final T Function() createBloc;
BlocHolder({
#required this.child,
#required this.createBloc
});
#override
_BlocHolderState createState() => _BlocHolderState();
}
class _BlocHolderState<T extends BlocBase> extends State<BlocHolder> {
T _bloc;
Function hello;
#override
void initState() {
super.initState();
_bloc = widget.createBloc();
}
#override
Widget build(BuildContext context) {
return BlocProvider(
child: widget.child,
bloc: _bloc,
);
}
#override
void dispose() {
_bloc.dispose();
super.dispose();
}
}
Bloc holder creates bloc in createState() and persists it. It also disposes bloc's resources in dispose().
class BlocProvider<T extends BlocBase> extends InheritedWidget {
final T bloc;
const BlocProvider({
Key key,
#required Widget child,
#required T bloc,
})
: assert(child != null),
bloc = bloc,
super(key: key, child: child);
static T of<T extends BlocBase>(BuildContext context) {
final provider = context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider;
return provider.bloc;
}
#override
bool updateShouldNotify(BlocProvider old) => false;
}
BlocProvider, as the name suggests, is only responsible for providing the bloc to nested widgets.
All the blocs extend BlocBase class
abstract class BlocBase {
void dispose();
}
Here's a usage example:
class RouteHome extends MaterialPageRoute<ScreenHome> {
RouteHome({List<ModelCategory> categories, int position}): super(builder:
(BuildContext ctx) => BlocHolder(
createBloc: () => BlocMain(ApiMain()),
child: ScreenHome(),
));
}
You are losing the state because your bloc is being retrieved in the _ProfileSettingsState's initState() thus, it won't change even when you hot-reload because that method is only called only once when the widget is built.
Either move it to the build() method, just before returning the BlocProvider
#override
Widget build(BuildContext context) {
_applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(context);
return BlocProvider<ApplicationStateBloc>(
bloc: _applicationStateBloc,
child: Scaffold(
backgroundColor: Colors.white,
....
or to the didUpdateWidget method which is called anytime the widget state is rebuild.
Have in mind that if you are using a non-broadcast stream in your bloc you may get an exception if you try to listen to a stream that is already being listened to.

Dispose widget when navigating to new route

I have two screens in my app.
Screen A runs a computationally expensive operation while opened, and properly disposes by cancelling animations/subscriptions to the database when dispose() is called to prevent memory leaks.
From Screen A, you can open another screen (Screen B).
When I use Navigator.pushNamed, Screen A remains in memory, and dispose() is not called, even though Screen B is now shown.
Is there a way to force disposal of Screen A when it is not in view?
Example code where first route is never disposed:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: FirstRoute(),
));
}
class FirstRoute extends StatefulWidget {
#override
_FirstRouteState createState() => _FirstRouteState();
}
class _FirstRouteState extends State<FirstRoute> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: RaisedButton(
child: Text('Open route'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
),
);
}
#override
void dispose() {
// Never called
print("Disposing first route");
super.dispose();
}
}
class SecondRoute extends StatefulWidget {
#override
_SecondRouteState createState() => _SecondRouteState();
}
class _SecondRouteState extends State<SecondRoute> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
);
}
#override
void dispose() {
print("Disposing second route");
super.dispose();
}
}
I know it's a bit late but I think you should override the deactivate method. Since we are changing the page we are not actually destroying it, that's why the dispose isn't being called.
If you'd like more information this page lists the lifecycle of the stateful widgets.
From the link:
'deactivate()' is called when State is removed from the tree, but it might be
reinserted before the current frame change is finished. This method exists basically
because State objects can be moved from one point in a tree to another.
call Navigator.pushReplacement when routing between first and second screen.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: FirstRoute(),
));
}
class FirstRoute extends StatefulWidget {
#override
_FirstRouteState createState() => _FirstRouteState();
}
class _FirstRouteState extends State<FirstRoute> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: RaisedButton(
child: Text('Open route'),
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
),
);
}
#override
void dispose() {
// Never called
print("Disposing first route");
super.dispose();
}
}
class SecondRoute extends StatefulWidget {
#override
_SecondRouteState createState() => _SecondRouteState();
}
class _SecondRouteState extends State<SecondRoute> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: RaisedButton(
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => FirstRoute()),
);
},
child: Text('Go back!'),
),
);
}
#override
void dispose() {
print("Disposing second route");
super.dispose();
}
}
Try this
In flutter new versions deactivate won't be called when you push a new widget on top of another widget. Also there is an open issue related to this topic on flutter github: https://github.com/flutter/flutter/issues/50147
The best way to handle this issue is to add RouteObserver<PageRoute> to your material app and override didPushNext and didPushNext functions.
There is a very helpful medium article related to this topic which you can find here: https://medium.com/koahealth/how-to-track-screen-transitions-in-flutter-with-routeobserver-733984a90dea
As Article said create your own RouteAwareWidget, you can add these two call backs to the fields of the widget:
didPopNext
didPushNext
class RouteAwareWidget extends StatefulWidget {
final Widget child;
final VoidCallback? didPopNext;
final VoidCallback? didPushNext;
const RouteAwareWidget({
Key? key,
required this.child,
this.didPopNext,
this.didPushNext,
}) : super(key: key);
#override
State<RouteAwareWidget> createState() => RouteAwareWidgetState();
}
class RouteAwareWidgetState extends State<RouteAwareWidget> with RouteAware {
#override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute);
}
#override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
#override
void didPush() {}
#override
void didPopNext() {
dPrint('didPopNext');
widget.didPopNext == null ? null : widget.didPopNext!();
super.didPopNext();
}
#override
void didPushNext() {
dPrint('didPushNext');
widget.didPushNext == null ? null : widget.didPushNext!();
super.didPushNext();
}
#override
Widget build(BuildContext context) => widget.child;
}
Create a global RouteObserver<PageRoute> and add it to your material app:
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
MaterialApp(
navigatorObservers: [routeObserver],
debugShowCheckedModeBanner: false,
routes: _routes,
)
then in your routs you should wrap your routes with RouteAwareWidget and add custom function you want:
final _routes = {
HomePage.routeName: (context) => RouteAwareWidget(
child: const HomePage(),
didPushNext: () => sl<CameraBloc>().add(Dispose()),
didPopNext: () => sl<CameraBloc>().add(Init()),
),
MyQuestions.routeName: (context) => const RouteAwareWidget(
child: MyQuestions(),
),
};
didPushNext will be called when you push a widget on top of HomePage and didPopNext will be called when you pop the last widget above HomePage.
With Navigator.pushReplacement(), if using MaterialPageRoute, then setting
maintainState:false
will ensure that dispose() is called.
A light weight solution for a single route case is using a callback function triggered from the SecondRoute.
Trigger the callback from the WidgetsBinding.instance.addPostFrameCallback() within the initState() on the SecondRoute
More information on WidgetsBinding and when they run can be found here: Flutter: SchedulerBinding vs WidgetsBinding.
WidgetsBinding & SchedulerBinding will be printed only once as we called it initState(), but it will be called when build method finished it’s rendering.
import 'package:flutter/material.dart';
class FirstRoute extends StatefulWidget {
const FirstRoute({super.key});
#override
State<FirstRoute> createState() => _FirstRouteState();
}
class _FirstRouteState extends State<FirstRoute> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Screen A')),
body: Center(
child: TextButton(
child: const Text('Go to Screen B'),
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => SecondRoute(_callbackFn),
),
);
_secondRouteDone();
},
),
),
);
}
_callbackFn() {
print("Widget B Loaded, Free up memory, dispose things, etc.");
}
_secondRouteDone() {
print("SecondRoute Popped, Reinstate controllers, etc.");
}
}
class SecondRoute extends StatefulWidget {
final Function() notifyIsMountedFn;
const SecondRoute(this.notifyIsMountedFn, {super.key});
#override
State<SecondRoute> createState() => _SecondRouteState();
}
class _SecondRouteState extends State<SecondRoute> {
#override
void initState() {
super.initState();
// Notify FirstRoute after paint
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.notifyIsMountedFn();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Screen B')),
);
}
}
Not only to call 'deactivate()' but also to use 'Navigator.pushReplacement()' for page moving is necessary. Not working if you are using 'Navigator.push()'.

How to dispose bloc when using Inherited Widget?

I have made a simple app using bloc and InheritedWidget.
Following is the code
class Bloc {
final StreamController<bool> _changeColor = PublishSubject<bool>();
Function(bool) get changeColour => _changeColor.sink.add;
Stream<bool> get colour => _changeColor.stream;
void dispose(){
_changeColor.close();
}
}
class Provider extends InheritedWidget {
final bloc = Bloc();
Provider({Key key,Widget child}): super(key: key,child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
static Bloc of(BuildContext context){
return (context.inheritFromWidgetOfExactType(Provider) as Provider).bloc;
}
void dispose(){
bloc?.dispose();
}
}
class _HomePageState extends State<HomePage> {
var bloc;
#override
void initState() {
super.initState();
bloc = Provider.of(context);
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
RaisedButton(
onPressed: () {
bloc.changeColour(true);
},
child: Text("Change colour"),
),
StreamBuilder(
builder: (context, snapshot) {
var bool = snapshot?.data ?? false;
return Text(
"First text",
style:
TextStyle(color: bool ? Colors.red : Colors.green),
);
},
stream: bloc?.colour,
),
],
);
}
#override
void dispose() {
super.dispose();
}
}
I don't understand how to call dispose method of the bloc when using InheritedWidget. Of course I can create a global variable of bloc and avoid using InheritedWidget to dispose the bloc using the dispose method which is present in the bloc but I really want to use InheritedWidget.
Does using the PublishSubject from rxdart disposes the streamcontroller automatically, is it life cycle aware, I couldn't find anything related to this in the documentation. Is there any debugging process to make sure the streamcontroller is disposed off correctly?
That is not possible using Inheritedwidget. The widget is not made to handle data, but to share it.
You have to wrap your Inheritedwidget into a StatefulWidget and use the dispose of the latter
To add to Remi's answer, the code would look something like this
import 'package:flutter/material.dart';
abstract class BlocBase {
void dispose();
}
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider({
Key key,
#required this.child,
#required this.bloc,
}): super(key: key);
final T bloc;
final Widget child;
#override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context){
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
return provider.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{
#override
void dispose(){
widget.bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context){
return widget.child;
}
}
class Bloc implements BlocBase {
final StreamController<bool> _changeColor = PublishSubject<bool>();
Function(bool) get changeColour => _changeColor.sink.add;
Stream<bool> get colour => _changeColor.stream;
#override
void dispose() {
_changeColor.close();
}
}
class _HomePageState extends State<HomePage> {
Bloc bloc;
var colour = false;
#override
void initState() {
super.initState();
bloc = BlocProvider.of<Bloc>(context);
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
RaisedButton(
onPressed: () {
if (colour) {
bloc.changeColour(false);
colour = false;
} else {
bloc.changeColour(true);
colour = true;
}
},
child: Text("Change colour"),
),
StreamBuilder(
builder: (context, snapshot) {
var bool = snapshot?.data ?? false;
return Text(
"First text",
style: TextStyle(color: bool ? Colors.red : Colors.green),
);
},
stream: bloc?.colour,
),
],
);
}
#override
void dispose() {
print("Bloc is disposed");
bloc.dispose();
super.dispose();
}
}

Flutter close a Dialog inside a condition

I am trying to close a Dialog dynamically.
What I am actually trying to do is to change the content of the dialog depending on the information I have at the moment.
Starts with loading info and no button and after a few seconds could be an error with the OK button to close the Dialog Box.
class Dialogs{
loginLoading(BuildContext context, String type, String description){
var descriptionBody;
if(type == "error"){
descriptionBody = CircleAvatar(
radius: 100.0,
maxRadius: 100.0,
child: new Icon(Icons.warning),
backgroundColor: Colors.redAccent,
);
} else {
descriptionBody = new Center(
child: new CircularProgressIndicator(),
);
}
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context){
return AlertDialog(
title: descriptionBody,
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Center(child: Text(description))
],
),
),
);
}
);
}
}
So after creating the instance os the dialog and opening it
Dialogs _dialog = new Dialogs();
_dialog.loginLoading(context, "loading", "loading...");
// Close the dialog code here
don't know how to do it
// Call again the AlertDialog with different content.
https://docs.flutter.io/flutter/material/showDialog.html
The dialog route created by this method is pushed to the root navigator. If the application has multiple Navigator objects, it may be necessary to call Navigator.of(context, rootNavigator: true).pop(result) to close the dialog rather than just Navigator.pop(context, result).
So any one of the below should work for you
Navigator.of(context, rootNavigator: true).pop(result)
Navigator.pop(context, result)
You don't need to close and reopen the dialog. Instead let flutter handle the dialog update. The framework is optimised for just that.
Here is a working example app that you can use as a starting point (just add your own Dialogs class):
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyApp',
home: Login(
child: Home(),
),
);
}
}
class Home extends StatefulWidget {
final Dialogs dialog = Dialogs();
#override
State<StatefulWidget> createState() => HomeState();
}
class HomeState extends State<Home> {
#override
void didChangeDependencies() {
super.didChangeDependencies();
Future.delayed(Duration(milliseconds: 50)).then((_) {
widget.dialog.loginLoading(
context,
LoginStateProvider.of(context).type,
LoginStateProvider.of(context).description,
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Updating Dialog'),
),
body: Container(),
);
}
}
class Login extends StatefulWidget {
final Widget child;
Login({#required this.child});
#override
State<StatefulWidget> createState() => LoginState();
}
class LoginState extends State<Login> {
String type = 'wait';
String description = 'foo';
#override
void didChangeDependencies() {
super.didChangeDependencies();
Future.delayed(Duration(milliseconds: 2000)).then((_) {
setState(() {
type = 'error';
description = 'bar';
});
});
}
#override
Widget build(BuildContext context) {
return LoginStateProvider(widget.child, type, description);
}
}
class LoginStateProvider extends InheritedWidget {
final String type;
final String description;
LoginStateProvider(Widget child, this.type, this.description)
: super(child: child);
#override
bool updateShouldNotify(LoginStateProvider old) {
return type != old.type || description != old.description;
}
static LoginStateProvider of(BuildContext context) =>
context.inheritFromWidgetOfExactType(LoginStateProvider);
}

Resources