Flutter Mobx Observer doesnt rebuild - flutter

I have no Idea anymore.
I am using Mobx for really simple State Management.
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:jw_helper/state/globalState.dart';
class Router extends StatelessWidget {
const Router({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final _globalState = GlobalState();
return Column(
children: <Widget>[
Container(
child: Observer(
builder: (_) => Text(_globalState?.currentIndex?.toString()),
),
),
MaterialButton(
onPressed: () {
_globalState.setCurrentIndex(1);
},
child: Text("Press me"),
),
],
);
}
}
When i mutate the state in this widget the value updates.
When i mutate the same Observable in another Widget the Observer is not rebuilding.
Only the observer in the same Widget where the State is mutated is updated.
My Mobx Code:
import 'package:mobx/mobx.dart';
// Include generated file
part 'globalState.g.dart';
// This is the class used by rest of your codebase
class GlobalState = _GlobalState with _$GlobalState;
// The store-class
abstract class _GlobalState with Store {
#observable
int currentIndex = 0;
#action
void setCurrentIndex(index) {
currentIndex = index;
print(currentIndex);
}
}
Small note: The Print Statement is always fired
Maybe someone knows how to fix this.
Thank you ;)

The Problem was solved with Help from a Discord Mobx Channel Member.
The solution was to wrap the whole App in a provider Widget.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
------------------------------------------------
return Provider<GlobalState>(
create: (context) => GlobalState(),
------------------------------------------------
child: MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SplashScreen(),
),
);
}
}
In the Widgets consuming the Mobx Class i did:
class Router extends StatelessWidget {
const Router({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final _globalState = Provider.of<GlobalState>(context);
return Column(
children: <Widget>[
Container(.....
Hopefully this helps somebody to get up and running ;)

Not a finished (nor will be) app but hopefully can help as a start.
Flutter + Mobx + (Multi) Providers
https://github.com/jonataswalker/flutter-example

You have two instances of GlobalState class. One for each widget. In order for Observer works correctly it needs to observe always the same instance.
Using "Provider" you are kind of using the Singleton pattern which solves the problem since both variables start refering to the same instance

Had same problem
Always rebuild mobX after any changes u place inside main store file
flutter pub run build_runner build --delete-conflicting-outputs

Related

Flutter: Why widget is rebuilding more than 10 times when I use StreamProvider even when value not changes?

I am watching a simple StreamProvider which just emits an int value. MyWidget is building only once if I am not assigning anything to theme variable but if I assign anything to theme then widget builds around 12 times.
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(streamProvider);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
textButtonTheme: TextButtonThemeData(
style: OutlinedButton.styleFrom(foregroundColor: Colors.red),
),
),
home: const MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
#override
Widget build(BuildContext context) {
print("#### build MyWidget");
return Container(
color: Theme.of(context).primaryColor,
);
}
}
final streamProvider = StreamProvider.autoDispose(((ref) => Stream.value(1)));
This is printing #### build MyWidget 12 times. If I do not read anything from Theme.of then it prints only once. What could be the issue here?
Edit
Everyone is saying it is the problem with Theme.of(context) but my confusion is why it is building only once if I convert ref.watch to ref.read?
As far as I can tell, this is not about Riverpod. You can remove any Riverpod dependencies and the problem will remain.
Also, I've narrowed down the problem a bit. Take a look at this code:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
print('#build $MyApp');
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(elevation: 1),
),
),
home: Builder(
builder: (context) {
print("#build Builder");
Theme.of(context);
return const SizedBox();
},
),
);
}
}
The problem appears whenever we define a new style in the line:
style: TextButton.styleFrom(elevation: 1),
If we write TextButton.styleFrom(), everything works as expected. Also, if we don't use Theme.of(context), that's fine too.
It is probably necessary to open issue :)
It is combination of many things.
By making MyApp's build dependent on streamProvider, you are saying that every time there is a value from stream, rebuild the widget tree. (Remove ref.watch and you will have only one build)
Having Theme widget as decedent of MyApp and using Theme.of(context) inside MyWidget you are saying that whenever there is change in context, MyWidget should be rebuilt. (Remove Theme.of(context) while keeping ref.watch and you will have only 1 build)
Every time a event is emitted, there are other widgets (part of build function of MaterialApp which gets rebuilt. AnimatedTheme, AnimatedTheme and MyWidget were marked dirty by framework in this case. You can verify it yourself by adding print('marking $this as dirty'); to flutter's framework.dart markNeedBuild method's end.
I think there is nothing wrong with either riverpod or flutter.
This is because of using StreamProvider.autoDispose which triggers the rebuilds in widgets that are using the stream.
You can use StreamProvider without .autoDispose which will only rebuild the widget that is listening to the stream changes.
final streamProvider = StreamProvider(((ref) => Stream.value(1)));
Have you considered the use of ref.listen instead of ref.watch? with 'listen' you can compare the new emitted value with the old, and SetState only if there is a change...
may stream is being re created or you try to assign value multiple times
sure you can try
import 'package:cloud_firestore/cloud_firestore.dart';
final CollectionReference myCollection = FirebaseFirestore.instance.collection('myCollection');
// Document reference for the document you want to delete
final DocumentReference documentReference = myCollection.doc('documentId');
// Call delete() on the DocumentReference to delete the document
documentReference.delete();

Flutter - Accessing a Provider from a higher point in the Widget tree

I've been working with Flutter recently, and I saw that there was many ways to deal with state management.
Following the recommendations there, I've been using Provider to deal with the state of my app.
I can update a part of my state from one of the widgets in my UI. To do that, I can call a method of the provider that's above the current widget in the context. No problems with this.
But I want the update of my state to be made from an overlay.
The issue is: When I'm inserting an OverlayEntry with Overlay.of(context)?.insert(), it inserts the overlayEntry to the closest Overlay, which is in general the root of the app, which is above the ChangeProvider. As a result, I get an exception saying I can't find the Provider from the OverlayEntry.
Here is a replication code I've been writting:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ChangeNotifierProvider(
create: (context) => NumberModel(), // All widgets that will be lower in the widget tree will have access to NumberModel
child: NumberDisplayer()
),
);
}
}
// Simple ChangeNotifier. We have a number that we can increment.
class NumberModel extends ChangeNotifier {
int _number = 10;
int get number => _number;
void add_one() {
_number = number + 1;
notifyListeners();
}
}
// This class displays a number, and a button.
class NumberDisplayer extends StatelessWidget {
NumberDisplayer({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var overlayEntry = OverlayEntry(builder: (context) =>
Positioned(
top: 100,
left: 50,
child: FloatingActionButton(onPressed: (){
// Throws "Error: Could not find the correct Provider<NumberModel> above this _OverlayEntryWidget Widget"
Provider.of<NumberModel>(context, listen: false).add_one();
})));
return Consumer<NumberModel>(
builder: (context, numberModel, child) {
return Column(
children: [
Text('Number: ${numberModel.number}'),
FloatingActionButton(onPressed: () {
Overlay.of(context)?.insert(overlayEntry);
})
],
);
},
);
}
}
I would like to find a way to update the information in my provider from the overlay, but I'm not sure how to approach this problem.
Thanks for your help everyone !

I use Stateless Widget alongside BLoC almost everytime. Am I wrong?

I am having a hard time understanding how to deal with states in some specific situations with Flutter.
For example, say I need a page where the click of a button fetches data from an API. Such a request could take time or any kind of problems could happen. For this reason, I would probably use the BLoC pattern to properly inform the user while the request goes through various "states" such as loading, done, failed and so on.
Now, say I have a page that uses a Timer to periodically (every 1sec) update a Text Widget with the new elapsed time value of a Stopwatch. A timer needs to be properly stopped (timer.cancel()) once it is not used anymore. For this reason, I would use a Stateful Widget and stop the timer directly in the dispose state.
However, what should one do when they have a page which both :
Fetches data from API and/or other services which require correctly handling states.
Uses a Timer or anything (streams ?) that requires proper canceling/closing/disposing of...
Currently, I have a page which makes API calls and also holds such a Timer. The page is dealt with the BLoC pattern and the presence of the Timer already makes the whole thing a little tedious. Indeed, creating a BLoC "just" to update a Timer feels a little bit like overkill.
But now, I am facing a problem. If the user doesn't go through the page the "regular" way and decides to use the "back button" of his phone: I never get to cancel the Timer. This, in turn, will throw the following error: Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
Indeed, even after having pop() to the previous page, the Timer is still running and trying to complete its every 1sec duty :
Timer.periodic(
const Duration(seconds: 1),
(Timer t) {
context.read<TimerBloc>().add(const Update());
},
);
}
Could someone please explain to me how such "specific" situations should be handled? There must be something, a tiny little concept that I am not totally understand, and I can feel it is slowing me down sometimes.
Thank you very much in advance for any help.
First off, this is opinionated.
Even though you've described a lot, it is a bit tricky to follow your cases and how you've (specifically) implemented it. But I'll give it a shot at describing things to consider.
There are several ways to handle this. I'll try to answer your questions.
There is nothing wrong with always or only having Stateless widgets and Blocs.
There is nothing wrong with combining Stateful widgets and Blocs.
Consider the case with a page with both a bloc and e.g. a Timer updating a particular text field on that page. Why should one widget handle both? It sounds like the page could be stateless (using the bloc), that has the text field in it, but that text field could/should perhaps be a separate StatefulWidget that only hold the timer, or equivalent. Meaning that sometimes people put to much responsibility in one huge widget, when it in fact should be split into several smaller ones.
I don't understand why you would face that error, it is no problem having both a bloc and a timer in a stateful widget, with poping and using backbutton with proper disposal and timer being reset. See the full code example below.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const FirstPage(),
);
}
}
class FirstPage extends StatelessWidget {
const FirstPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First page'),
),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BlocProvider<FetcherCubit>(
create: (context) => FetcherCubit(),
child: const SecondPage(),
),
)),
child: const Text('Second page')),
),
);
}
}
class SecondPage extends StatefulWidget {
const SecondPage({super.key});
#override
State<SecondPage> createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
late final Timer myTimer;
int value = 0;
#override
void initState() {
super.initState();
myTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
value++;
});
});
}
#override
void dispose() {
super.dispose();
myTimer.cancel();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Timer value: ${value.toString()}'),
ElevatedButton(
onPressed: () => context.read<FetcherCubit>().fetch(),
child: const Text('Fetch!'),
),
BlocBuilder<FetcherCubit, FetcherState>(
builder: (context, state) {
late final String text;
if (state is FetcherInitial) {
text = 'Initial';
} else if (state is FetcherLoading) {
text = 'Loading';
} else {
text = 'Completed';
}
return Text(text);
},
)
],
),
),
);
}
}
class FetcherCubit extends Cubit<FetcherState> {
FetcherCubit() : super(FetcherInitial());
Future<void> fetch() async {
emit(FetcherLoading());
await Future.delayed(const Duration(seconds: 3));
emit(FetcherCompleted());
}
}
#immutable
abstract class FetcherState {}
class FetcherInitial extends FetcherState {}
class FetcherLoading extends FetcherState {}
class FetcherCompleted extends FetcherState {}
The result if you build it:

Consumer and context.watch in MultiProvider

I am trying Flutter for the first time, and I am a little confused by the MultiProvider class.
The question is straightforward, but I didn't find an explanation:
when should one use Consumer and when context.watch?
For instance, taking one of the examples apps I have found, I tried using two providers for two global states, the theme and the status of the app:
runApp(
MultiProvider(providers: [
ChangeNotifierProvider(create: (context) => AppTheme()),
ChangeNotifierProvider(create: (context) => AppStatus()),
],
child: const MyApp()
));
Then the app widget accesses the theme with Consumer:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<AppTheme>(
builder: (context, appTheme, child) {
// ...
As far as I understand, now all children widgets will inherit the provider. Is it right?
My home page, then, called by the MyApp class does not use Consumer, but context.watch:
#override
Widget build(BuildContext context) {
final appTheme = context.watch<AppTheme>();
final appStatus = context.watch<AppStatus>();
return NavigationView(
// ...
It works, don't get me wrong, but I just copied the row above my appStatus, so I don't really fully understand it. This is also due to another screen that I've concocted to access the AppStatus global state, but I use Consumer, as suggested by the Flutter documentation:
class _ViewerState extends State<Viewer> {
#override
Widget build(BuildContext context) {
return Consumer<AppStatus>(
builder: (context, appStatus, child) {
return ScaffoldPage.scrollable(
header: const PageHeader(title: Text('Test')),
children: [
FilledButton(child: Text("Try ${appStatus.count}"), onPressed: (){ appStatus.increment(); debugPrint('pressed ${appStatus.count}'); }),
FilledButton(child: Text("Reset"), onPressed: (){ appStatus.reset(); }),
]);
},
);
}
}
I have the feeling that I am misusing something here, and I do not really understand what's going on under the hood...
context.watch<T>() and Consumer<T> does the same thing. Most of the time context.watch<T>() is just more convenient. In some cases where context is not available Consumer<T> is useful.

Flutter StatefulWidget hierarchy [duplicate]

For various reasons, sometimes the build method of my widgets is called again.
I know that it happens because a parent updated. But this causes undesired effects.
A typical situation where it causes problems is when using FutureBuilder this way:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: httpCall(),
builder: (context, snapshot) {
// create some layout here
},
);
}
In this example, if the build method were to be called again, it would trigger another HTTP request. Which is undesired.
Considering this, how to deal with the unwanted build? Is there any way to prevent a build call?
The build method is designed in such a way that it should be pure/without side effects. This is because many external factors can trigger a new widget build, such as:
Route pop/push
Screen resize, usually due to keyboard appearance or orientation change
The parent widget recreated its child
An InheritedWidget the widget depends on (Class.of(context) pattern) change
This means that the build method should not trigger an http call or modify any state.
How is this related to the question?
The problem you are facing is that your build method has side effects/is not pure, making extraneous build calls troublesome.
Instead of preventing build calls, you should make your build method pure, so that it can be called anytime without impact.
In the case of your example, you'd transform your widget into a StatefulWidget then extract that HTTP call to the initState of your State:
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
Future<int> future;
#override
void initState() {
future = Future.value(42);
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
}
I know this already. I came here because I really want to optimize rebuilds
It is also possible to make a widget capable of rebuilding without forcing its children to build too.
When the instance of a widget stays the same; Flutter purposefully won't rebuild children. It implies that you can cache parts of your widget tree to prevent unnecessary rebuilds.
The easiest way is to use dart const constructors:
#override
Widget build(BuildContext context) {
return const DecoratedBox(
decoration: BoxDecoration(),
child: Text("Hello World"),
);
}
Thanks to that const keyword, the instance of DecoratedBox will stay the same even if the build was called hundreds of times.
But you can achieve the same result manually:
#override
Widget build(BuildContext context) {
final subtree = MyWidget(
child: Text("Hello World")
);
return StreamBuilder<String>(
stream: stream,
initialData: "Foo",
builder: (context, snapshot) {
return Column(
children: <Widget>[
Text(snapshot.data),
subtree,
],
);
},
);
}
In this example when StreamBuilder is notified of new values, subtree won't rebuild even if the StreamBuilder/Column does.
It happens because, thanks to the closure, the instance of MyWidget didn't change.
This pattern is used a lot in animations. Typical uses are AnimatedBuilder and all transitions such as AlignTransition.
You could also store subtree into a field of your class, although less recommended as it breaks the hot-reload feature.
You can prevent unwanted build calling, using these way
Create child Statefull class for individual small part of UI
Use Provider library, so using it you can stop unwanted build method calling
In these below situation build method call
After calling initState
After calling didUpdateWidget
when setState() is called.
when keyboard is open
when screen orientation changed
If Parent widget is build then child widget also rebuild
Flutter also has ValueListenableBuilder<T> class . It allows you to rebuild only some of the widgets necessary for your purpose and skip the expensive widgets.
you can see the documents here ValueListenableBuilder flutter docs
or just the sample code below:
return Scaffold(
appBar: AppBar(
title: Text(widget.title)
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
ValueListenableBuilder(
builder: (BuildContext context, int value, Widget child) {
// This builder will only get called when the _counter
// is updated.
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text('$value'),
child,
],
);
},
valueListenable: _counter,
// The child parameter is most helpful if the child is
// expensive to build and does not depend on the value from
// the notifier.
child: goodJob,
)
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.plus_one),
onPressed: () => _counter.value += 1,
),
);
One of the easiest ways to avoid unwanted reBuilds that are caused usually by calling setState() in order to update only a specific Widget and not refreshing the whole page, is to cut that part of your code and wrap it as an independent Widget in another Stateful class.
For example in following code, Build method of parent page is called over and over by pressing the FAB button:
import 'package:flutter/material.dart';
void main() {
runApp(TestApp());
}
class TestApp extends StatefulWidget {
#override
_TestAppState createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
int c = 0;
#override
Widget build(BuildContext context) {
print('build is called');
return MaterialApp(home: Scaffold(
appBar: AppBar(
title: Text('my test app'),
),
body: Center(child:Text('this is a test page')),
floatingActionButton: FloatingActionButton(
onPressed: (){
setState(() {
c++;
});
},
tooltip: 'Increment',
child: Icon(Icons.wb_incandescent_outlined, color: (c % 2) == 0 ? Colors.white : Colors.black)
)
));
}
}
But if you separate the FloatingActionButton widget in another class with its own life cycle, setState() method does not cause the parent class Build method to re-run:
import 'package:flutter/material.dart';
import 'package:flutter_app_mohsen/widgets/my_widget.dart';
void main() {
runApp(TestApp());
}
class TestApp extends StatefulWidget {
#override
_TestAppState createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
int c = 0;
#override
Widget build(BuildContext context) {
print('build is called');
return MaterialApp(home: Scaffold(
appBar: AppBar(
title: Text('my test app'),
),
body: Center(child:Text('this is a test page')),
floatingActionButton: MyWidget(number: c)
));
}
}
and the MyWidget class:
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
int number;
MyWidget({this.number});
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
#override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: (){
setState(() {
widget.number++;
});
},
tooltip: 'Increment',
child: Icon(Icons.wb_incandescent_outlined, color: (widget.number % 2) == 0 ? Colors.white : Colors.black)
);
}
}
I just want to share my experience of unwanted widget build mainly due to context but I found a way that is very effective for
Route pop/push
So you need to use Navigator.pushReplacement() so that the context of the previous page has no relation with the upcoming page
Use Navigator.pushReplacement() for navigating from the first page to Second
In second page again we need to use Navigator.pushReplacement()
In appBar we add -
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pushReplacement(
context,
RightToLeft(page: MyHomePage()),
);
},
)
In this way we can optimize our app
You can do something like this:
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
Future<int> future;
#override
void initState() {
future = httpCall();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
void refresh(){
setState((){
future = httpCall();
});
}
}