I'm trying to access context so i can read my provider but since this lifecycle hook is out side the widget tree. it's not accessible. is there a way to get access to context?
I researched a little bit and finally discussed with narcodico from the flutter bloc community, so the credits are for him.
Therefore, mixin WidgetsBindingObserver on a state class, the context is available even in the overrides like didChangeAppLifecycleState since they are part of the state class.
Also, take in consideration to move to BlocProvider above the state widget.
Example
class HomePageProvider extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<InAppPurchasesBloc>(),
child: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomePageState();
}
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
...
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
context
.read<InAppPurchasesBloc>()
.add(const InAppPurchasesEvent.getPurchaserInfo());
}
}
...
}
I am afraid you can't access context inside didChangeAppLifecycleState.
For anyone interested, you can save your scaffold state in a global key, and access the context from its current state.
You can use useEffect function, read more:
https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useEffect.html;
Widget build(BuildContext context) {
useEffect(() {
//what would you write in initState
},
);
You may consider using the Riverpod package instead of Provider. Riverpod is from the same author as Provider and considered the "better Provider", but with many improvements including Flutter independence, meaning it does not rely on a context to work, and you can use it almost the same way as provider.
Using Riverpod, along with Flutter Hooks, you can do something like:
// create a provider in a global context
final myProvider = Provider((ref) => myClass());
// access the provider inside your class
class MyWidget extends HookWidget{
//access the provider using a hook
final myClassProvider = useProvider(myProvider);
//... your logic
#override
Widget build (BuildContext context){/* ... build widget tree... */}
}
Consider this very useful and concise tutorial with how to use Riverpod with Flutter Hooks and StateNotifier, ChangeNotifier, etc...
Related
I'm trying to implement a clean architecture with no dependency of the framework in the business' logic layers.
The following example is a Screen with only a Text. I make an API Rest call in the repository and add the response to a BehaviorSubject that is listened through a StreamBuilder that will update the Text. Since is an StatefulWidget I'm using the dispose method to close the BehaviorSubject's StreamController.
The example is simplified, no error/loading state handling, no dependency injection, base classes, dispose interfaces etc.
class Bloc {
final UserReposiotry _userReposiotry;
final BehaviorSubject<int> _activeUsersCount = BehaviorSubject.seeded(0);
Bloc(this._userReposiotry) {
_getActiveUsersCount();
}
void _getActiveUsersCount() async {
final response = await _userReposiotry.getActiveUsersCount();
_activeUsersCount.add(response.data);
}
ValueStream<int> get activeUsersCount => _activeUsersCount.stream;
void dispose() async {
await _activeUsersCount.drain(0);
_activeUsersCount.close();
}
}
class StatefulScreen extends StatefulWidget {
final Bloc bloc;
const StatefulScreen({Key? key, required this.bloc}) : super(key: key);
#override
State<StatefulScreen> createState() => _StatefulScreenState();
}
class _StatefulScreenState extends State<StatefulScreen> {
#override
Widget build(BuildContext context) {
final stream = widget.bloc.activeUsersCount;
return StreamBuilder<int>(
stream: stream,
initialData: stream.value,
builder: (context, snapshot) {
return Text(snapshot.data.toString());
}
);
}
#override
void dispose() {
widget.bloc.dispose();
super.dispose();
}
}
I have the following doubts regarding this approach.
StreamBuilder cancels the stream subscription automatically, but it doesn't close the StreamController. I know that you should close it if you are reading a file, but in this case, if I don't manually close it, once the StatefulScreen is no longer in the navigation stack, could it be destroyed, or it would be a memory leak?
I've seen a lot of people using StatelessWidget instead of StatefulWidget using Stream and StreamBuilder approach, if it is really needed to close the BehaviorSubject it is a problem since we don't have the dispose method, I found about the WillPopScope but it won't fire in all navigation cases and also and more important would it be more performant an approach like WillPopScope, or having an StatefulWidget wrapper (BlocProvider) inside an StatelessWidget just to do the dispose, than using an StatefulWidget directly, and if so could you point to an example of that implementation?
I'm currently choosing StatefulWidget for widgets that have animations o controllers (map, text input, pageview...) or streams that I need to close, the rest StatelessWidget, is this correct or am I missing something?
About the drain method, I'm using it because I've encountered an error navigating back while an API rest call was on progress, I found a member of the RxDart team saying it isn't really necessary to call drain so I'm confused about this too..., the error:
You cannot close the subject while items are being added from addStream
Thanks for your time.
I have seen many times people calling widget. sth inside the code.
May I know what it is actually doing?
For example code below, (highlighted part is my confusion)
class _MyOwnClassState extends State<MyOwnClass> {
#override
Widget build(BuildContext context) {
return ListTile(
title: Container(
child: Column(children: makeWidgetChildren(**widget.jsonObject)**),
),
);
}
}
In flutter's StatefulWidget, we have the following architecture.
You have a StatefulWidget like this,
class MyOwnClass extends StatefulWidget {
State createState () => _MyOwnClassState();
}
And you have a State class for your StatefulWidget like this,
class _MyOwnClassState extends State<MyOwnClass> {
}
Now, State class is meant to house variables that tend to change in order for your UI to be rebuilt.
So you can have variables in your State that you can update using setState.
But what if you had some data that doesn't change and you want to avoid putting them inside the State class.
That's where your StatefulWidget comes to play.
You can store variables in your MyOwnClass and the widget variable inside the State class gives you a way to access them.
For example,
class MyOwnClass extends StatefulWidget {
int numberThatDoesntChange = 1;
State createState () => _MyOwnClassState();
}
You can access them in your State class like this,
class _MyOwnClassState extends State<MyOwnClass> {
Widget build(BuildContext context) {
return Text('$widget.numberThatDoesntChange');
}
}
Apart from this, your StatefulWidget has many more internal instance members that you can access inside of your State class using the widget variable.
The widget refers to the actual view that renders on the screen. It extends the StatefulWidget class of the flutter framework and overrides the createState() method. The createState() method is used to create the instance of state class. We will look into createState().
The state class is used to maintain the state of the widget so that it can be rebuilt again. It extends the State class of the flutter framework and overrides the build method.
The framework calls build() method again and again whenever setState() method is called. The setState() method notifies the framework that the internal state of this object has changed and it should be rebuilt. Suppose we change the value of text in StatefulWidget then we need to call setState().
Edit As Nisanth pointed outh in his comment - I missed your question completely; please ignore the below....
Let me try my answer, I don't think others are getting your point.
In your exapmle, Column(children: x) expect a list of Widgets.
You have two options - either provide this list directly:
class _MyOwnClassState extends State<MyOwnClass> {
#override
Widget build(BuildContext context) {
return ListTile(
title: Container(
child: Column(children: <Widget>[SomeWidget()]),
),
);
}
}
Or if you have more complex code that generates widget - based on input parameters, or you have the same widget generated multiple times and you want to avoid the code duplication - you would create the separate function to do the job.
Something like:
class _MyOwnClassState extends State<MyOwnClass> {
List<Widget> makeWidgetChildren(int param) {
/*
some very complex logic here
/*
if (param>3 && param<4) {
return List<Widget>.generate(4, (index)=>SomeWidget1(index));
} else {
return <Widget>[Center()];
}
}
#override
Widget build(BuildContext context) {
return ListTile(
title: Container(
child: Column(children: makeWidgetChildren(**widget.jsonObject)**),
),
);
}
}
So basically, it is just to make the code nicer; and to avoid having code repeated over and over again in the build function.
In short, does the context belong in the BLoC class and if it doesn't, what's the right approach?
I'm using a Provider as an abstraction layer between the Firebase DB and the UI. Recently, we've been abstracting further away to use the BLoC pattern, so that the widgets don't manipulate the data in the Provider directly. It's all proceeding nicely, but due to the fact that we use both the providers and the BLoC, I am not sure how to use the BuildContext properly, as the context ha more to do with the Widgets/UI than the business logic.
Here's an example:
class SomeWidget extends StatelessWidget {
final SomeWidgetBloc bloc;
SomeWidget({Key key, this.bloc});
#override
Widget build(BuildContext context) => StreamBuilder(stream: bloc.getSomeData,
builder: (context, snapshot) {
return Text(snapshot.data ?? "Empty");
}
}
class SomeWidgetBloc {
BuildContext context; // should it be here? Currently, it's needed for the Provider
SomeWidgetBloc(BuildContext context);
Stream<String> get getSomeData {
return Provider.of<SomeFirebaseProvider>(context).fetchSomeData();
}
}
You should pass your SomeFirebaseProvider instance to the SomeWidgetBloc constructor at creation time.
Is there a widget equivalent of the ChangeNotifierProvider widget of Provider in Riverpod?
The use case is to create a provider only when a page whose parent widget is ChangeNotifierProvider/or a page that has ChangeNotifierProvider in its widget tree has been pushed unto the Navigator stack using create. I would like the provider to be automatically disposed when the page is popped and the ChangeNotifierProvider widget is removed from the widget tree just like in Provider.
Riverpod has a ChangeNotifierProvider too, so you can use that.
As for the "I would like the provider to be automatically disposed when the page is popped", this functionality is instead implemented using autoDispose
So in the end, the syntax would be:
class MyNotifier extends ChangeNotifier {}
final myNotifierProvider = ChangeNotifierProvider.autoDispose<MyNotifier>((ref) {
return MyNotifier();
});
...
class MyWidget extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
MyNotifier myNotifier = watch(myNotifierProvider);
}
}
With this, when all the widgets using MyNotifier are destroyed (aka when the route is popped), then MyNotifier will be disposed.
My app uses a set of Card()s inside a PageView(), each card has multiple text lines.
The user gives input for each line (e.g. modifying the text etc.).
I want to keep this input for a while.
This is how my code looks at the moment, abstractly speaking:
class MyCard extends StatefulWidget {
final List<Widget> _myLines = [];
#override
State<StatefulWidget> createState() => MyCardState();
}
class MyCardState extends State<MyCard> {
...
#override
Widget build(BuildContext context) {
...
widget._myLines.add(ChangeNotifierProvider(
create: (context) => MyLineModel(context, lineText),
child: RecipeLine())
...
}
}
This doesn't work well:
As soon as I swipe left / right through the PageView onto other cards and then swipe back, the Card is being built again. This also leads to a rebuild of MyLineModel, which in turn erases all the user's input.
How can I avoid the rebuild of MyLineModel and keep the user's input?
You can solve this in 2 ways:
Create the model outside the widget and pass the model for the widget, as a variable to a constructor for example, or using Provider or any other technique of Dependency Injection.
Using any of the KeepAlive APIs, such as the AutomaticKeepAliveClientMixin as:
class MyCardState extends State<MyCard> with AutomaticKeepAliveClientMixin {
...
#override
bool get wantKeepAlive => true;
}
As the official documentation states, we can simply use the ChangeNotifierProvider.value() constructor...
In the original code:
class MyCardState extends State<MyCard> {
...
#override
Widget build(BuildContext context) {
...
widget._myLines.add(ChangeNotifierProvider.value(
value: MyLineModel(context, lineText),
child: RecipeLine())
...
}
}