Flutter best practices share data between screen - flutter

I'm new in flutter and I read lot of documentations on multiple subjets. One of them is sharing data between screens. I found a lot of solutions and as I'm creating a base project with lot of features, I want to know which one is the best
Solution 1 : In constructor
Basically, when we navigate, we send the data through the next constructor widget.
Solution 2 : In session
Creates a singleton which can be access from everywhere in the application and each time you need to send data, you add it in the session and the next widget can retrieve it.
Solution 3 : In blocs
I read this solution which looks good:
I made a BLoC container class where I've instantiated the BLoCs of the two screen. Then here I set the reference from the BLoC A to a stream of the BLoC B where I want to send the data. The BLoCs are still decoupled cause they don't know anything about each other, the BLoC A doesn't get passed through the constructor on the BLoC B and vice versa, but it just knows that it will receive some data on one of its streams.
UPDATED:
Solution 4 : Inherited widget
With a static function like :
static InheritedDataProvider of(BuildContext context) => context.inheritFromWidgetOfExactType(InheritedDataProvider);
}
So you can access to the data initialized in the parent with something like :
final data = InheritedDataProvider.of(context).data;
Maybe there are others solutions and I'll be glad to know them.
Thanks

The best way is passing a parameter to the constructor
Navigator.push(
context,
PageTransition(
type: PageTransitionType.fade,
child: LoginPage(
userModel: model,
)));
then :
class LoginPage extends StatefulWidget {
LoginPage({this.userModel});
User userModel;
#override
_LoginPageState createState() => _LoginPageState(userModel: userModel);
}
class _LoginPageState extends State with TickerProviderStateMixin {
User userModel;
_LoginPageState({this.userModel});
}
}

What I use:
Call next screen with Navigator.pushNamed(ctx, '/foo', arguments: bar). Only include arguments if needed (e.g. for "update detail" screen).
Centralize all possible routes in the widget that contains MaterialApp.
Pass a clone of the passed argument to the next screen (immutable data).
In code:
...
MaterialApp(
onGenerateRoute: (s) => MaterialPageRoute(builder: (ctx) => _route(s), settings: s),
)
...
Widget _route(RouteSettings route) {
switch (route.name) {
case '/signup':
return SignupRoute();
case '/vendor':
return VendorRoute((route.arguments as Vendor)?.data?.clone());
default:
throw ('No match for ${route.name}');
}
}

Related

Where to put bloc providers in flutter

I have tried to use bloc in flutter for a period, but the question of where to put bloc provider confuse me.
At the beginning of learning flutter, I put nearly all the providers in the main() function by using MultipleBlocProvider.
But due to some of my blocs subscribe to streams such as firebase snapshots, I think it might cost extra resources (I am not sure but at least some memory was occupied I guess) if I do not close those subscriptions by closing the bloc.
However, if the provider is in the main(), change page or pop up would not close these blocs and the subscriptions inside.
In this scenario, I try to put some bloc providers into specific pages, so the bloc can be close and recreate when I goes in and out of this page. But there are some other questions occurs.
For example, if I have a bloc called ProductDetailsBloc which is used to control the widget of product details in product details page, its events contains an event called GetProductBySku which is used to get product from firebase and set the product inside the state(ProductDetailsState).
I want to put this bloc and its provider inside product details page and put an event trigger inside product list widget (located in product list page) and its onTap() function, where users click on the product list item (this is a thumbnail product item which is from another resources and only contains very basic info such as sku, title ) inside product list page and then navigator to product details page to view the full information of this product.
But as I mentioned before, If I put the bloc provider of ProductDetailsBloc inside product details page, I can not use GetProductBySku before entering product details page
So, I personally have two ideas for this questions,
the first one is passing the product sku through arguments to product details page and then call the GetProductBySku after the bloc has been created.
the second one is the put the ProductDetailsBloc inside the main(), so I can skip this questions, and directly use the GetProductBySku in product list widget, but this turns the problems in to the very front.
I do not know which one is better, so I would be very appreciative if someone can give some suggestions.
And back to the main questions, what is the best practice of putting bloc providers and what are the concerns if I put providers inside main().
Thanks for every one that read to here because this is a bit long and this is my first time of asking on attack overflow :3
You can find more discussion regarding this in the discord server of Bloc. More info: https://discord.com/channels/649708778631200778/649708993773699083/860604230930006016
I will copy paste the message and code here. we have been using bloc
in our app for over a year and it seems we have got an issue.
Scenario: List of Posts needs to be shown in home feed. Let's say each
post is encapsulated in PostBloc (operations on post to be performed
here). I have been using moor to reactively find if anything changes
in the data (like number of likes of post which might have trigged
from post detail page where post detail was fetched again). I want to
know if it is a wise decision to create BlocFactory which will be
providing Bloc object (here different PostBloc object) based on
Post_Id.
I have written a sample BlocFactoryProvider, please let me know your
thoughts
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
typedef CreateIfAbsent<T> = T Function();
//create bloc of <T> type
//author: https://github.com/imrhk
class BlocFactory<T extends Bloc<dynamic, dynamic>> {
Map<String, T> _cache = <String, T>{};
T createBloc({#required String id, #required CreateIfAbsent<T> createIfAbsent}) {
assert(id != null && createIfAbsent != null);
if (_cache.containsKey(id)) {
return _cache[id];
}
final value = createIfAbsent.call();
_cache[id] = value;
return value;
}
void dispose() {
_cache.values.forEach((bloc) => bloc?.close());
_cache.clear();
}
}
class BlocFactoryProvider<T extends Bloc<dynamic, dynamic>, V>
extends SingleChildStatelessWidget {
final BlocFactory<T> _blocFactory;
final Widget child;
BlocFactoryProvider({BlocFactory<T> blocFactory, this.child})
: _blocFactory = blocFactory ?? BlocFactory<T>();
#override
Widget buildWithChild(BuildContext context, Widget child) {
return InheritedProvider<BlocFactory<T>>(
create: (_) => _blocFactory,
dispose: (_, __) => _blocFactory.dispose(),
child: child,
lazy: false,
);
}
static BlocFactory<T> of<T extends Bloc<dynamic, dynamic>>(BuildContext context) {
try {
return Provider.of<BlocFactory<T>>(context, listen: false);
} on ProviderNotFoundException catch (e) {
if (e.valueType != T) rethrow;
throw FlutterError(
"""
BlocProvider.of() called with a context that does not contain a Bloc of type $T.
No ancestor could be found starting from the context that was passed to BlocProvider.of<$T>().
This can happen if the context you used comes from a widget above the BlocProvider.
The context used was: $context
""",
);
}
}
Felix's replay was
It’s hard to say without more context but I’d recommend moving the
caching to the repository layer and instead have a single PostBloc
with PostChanged event that pulls the latest details (using cache).
So he did not completely discarded the idea.
declaration: I am the author of the below code shared over discord in March'21.

How to get the State<> instance inside of its StatefulWidget?

I have an unusual use case where I'd like to add a getter to a StatefulWidget class that accesses its State instance. Something like this:
class Foo extends StatefulWidget {
Foo({super.key});
int get bar => SomethingThatGetsFooState.bar;
#override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
int bar = 42;
#override
Widget build(BuildContext context) {
return Container();
}
}
Does SomethingThatGetsFooState exist?
I wonder, if your approach is the right way.
Flutter's way isn't 'Ask something about its state'
Flutter is much more like this: 'The consumer of a Widget passes something to another Widget, which the other Widget e.g. calls in case of certain situations (e.g. value change).'
Approach 1
You map pass a Callback Function to Foo and pass that along to _FooState.
If something special happens inside _FooState, you may call the callback and thus pass some value back to the provider of the Callback.
Approach 2
Probably use a state management solution like Flutter Redux. Using Flutter Redux, you establish a state store somewhere at the top of the widget tree, e.g. in MaterialApp.
Then you subscribe to the store at certain other locations, where dependent widgets exist, which need to update accordingly.
In one project I created certain Action classes, which I send to certain so called reducers of those states, to perform a change on the state:
StoreProvider.of<EModel>(context).dispatch( SaveToFileAction())
This call finds the relevant EModel state and asks it to perform the SaveToFileAction().
This way, a calling Widget not even needs to know, who is responsible for the Action.
The responsible Widget can even be moved around the widget tree - and the application still works. The initiator of SaveToFileAction() and the receiver are decoupled. The receiver you told a coordination 'Tell me, if someone tried to ask me for something.'
Could your provide some further details? Describe the usage pattern?
#SteAp is correct for suggesting there's a code smell in my OP. Typically there's no need to access State thru its StatefulWidget. But as I responded to his post, I'm fleshing out the first pass at a state management package, so it's an unusual case. Once I get it working, I'll revisit.
Below worked without Flutter complaining.
class _IntHolder {
int value;
}
class Foo extends StatefulWidget {
Foo({super.key});
final _intHolder = _IntHolder();
int get bar => _intHolder.value;
#override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
int value = 42;
#override
Widget build(BuildContext context) {
widget._intHolder.value = value;
return Container();
}
}

How to get TextField content from screen to screen?

still new to flutter and I was trying to take multiple values from TextFields in a form to display them in a new screen inside multiple Text elements.
Can someone explain how to do it ?
There are three ways to do it
First method: You can define a class and assign values to it like this:
class Global(){
String text;
}
and then you can import it and assign values or use it like this:
// assign data
Global().text = TextField_controller; // I assume you have already implemented a TextField
// use it
Text(Global().text)
This method is good for passing data between multiple pages but it's not recommended because you can't update the screen when the value changes, it's only good when you need to pass a static variable between multiple pages, for example a user name
Second method: passing data to next page directly
Make the SecondScreen constructor take a parameter for the type of data that you want to send to it. In this particular example, the data is defined to be a String value and is set here with this.text.
class SecondScreen extends StatelessWidget {
final String text;
SecondScreen({Key key, #required this.text}) : super(key: key);
...
Then use the Navigator in the FirstScreen widget to push a route to the SecondScreen widget. You put the data that you want to send as a parameter in its constructor.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(text: 'Hello',),
));
this method is great for passing data from a parent page to a child page however it can quickly become a nightmare if you want to pass the data to several children down the widget tree or move data back to the parent widget, in that case you can use method 1 or
Third method: using Provider, which is the recommended way, it is similar to the first method but with provider you can ``notify``` all of the listeners to the provider class, meaning you can update the widget whenever the the variable updates, I strongly recommend reading the documentation or watching some YouTube videos, but in short you can use it like this:
after installing the provider package you define your class:
Global extends ChangeNotifierProvider(){
String text;
}
and then add it to the root of your app in main.dart:
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Global()),
),
],
child: MyApp(),
);
}
and then you define your provider wherever you need to use it
Provider.of<Global>(context, listen: false); // Note that if you want to listen to changes you have to set listen to true
// then you can access your variable like in method 1
insatnce.text = TextField_controller;
// and then you can use it anywhere
Text(instance.text);
again if you find this confusing read the documentation or watch some videos

flutter:: Is it possible to get string from another file in stateful?

How to get data from previous page when using stateful String link.
Is it possible to get string from another file in stateful?
I need to get the string from another file b into the stateful of file a. In case of stateless this was possible, but in stateful it is not possible.
Is there a way to solve this?
I hate to disagree but using global variables in this situation when you can fix your problem easily is not wise although it works and it is very easy.
if you want to send data to another page via pushing a new page you can always use that newPage Constructor
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BalancePage(
items: "item",
),
),
);
New Page
class BalancePage extends StatefulWidget {
String? items;
BalancePage({
this.items,
});
#override
_BalancePageState createState() => _BalancePageState();
}
if you want to take data from another class you can set a getter
class newVal(){
String val ="ss";
String getVal(){
return val;
}
}
and you can call it anywhere like this
String ss = newVal().getVal;

I am losing stream data when navigating to another screen

I am new to Dart/Flutter and after "attending" a Udemy course,
everything has been going well.
Until now ;-)
As in the sample application in the Udemy course i am using the BLOC pattern.
Like this:
class App extends StatelessWidget {
Widget build(context) {
return AppBlocProvider(
child: MaterialApp(
(See "AppBlocProvider" which I later on use to get the "AppBloc")
The App as well as all the screens are StatelessWidget's.
The AppBlocProvider extends the InheritedWidget.
Like this:
class AppBlocProvider extends InheritedWidget {
final AppBloc bloc;
AppBlocProvider({Key key, Widget child})
: bloc = AppBloc(),
super(key: key, child: child);
bool updateShouldNotify(_) => true;
static AppBloc of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(AppBlocProvider) as AppBlocProvider).bloc;
}
}
The AppBlocProvider provides an "AppBloc" containing two further bloc's to separate the different data a bit.
Like this:
class AppBloc {
//Variables holding the continuous state
Locale appLocale;
final UserBloc userBloc;
final GroupsBloc groupsBlock;
In my application I have a "GroupSearchScreen" with just one entry field, where you can enter a fragment of a group name. When clicking a button, a REST API call is done and list of group names is returned.
As in the sample application, I put the data in a stream too.
In the sample application the data fetching and putting it in the stream is done in the bloc itself.
On the next line, the screen that uses the data, is created.
Like this:
//Collecting data and putting it in the stream
storiesBloc.fetchTopIds();
//Creating a screen ths shows a list
return NewsList();
In my case however, there are two major differences:
After collecting the data in the GroupSearchScreen, I call/create the GroupsListScreen, where the list of groups shall be shown, using regular routing.
Like this:
//Add data to stream ("changeGroupList" privides the add function of the stream!)
appBloc.groupsBlock.changeGroupList(groups);
//Call/create screen to show list of groups
Navigator.pushNamed(context, '/groups_list');
In the GroupsListScreen, that is created, I fetch the bloc.
Like this:
Widget build(context) {
final AppBloc appBloc = AppBlocProvider.of(context);
These are the routes:
Route routes(RouteSettings settings) {
switch (settings.name) {
case '/':
return createLoginScreen();
case '/search_group':
return createSearchGroupScreen();
case '/groups_list':
return createGroupsListScreen();
default:
return null;
}
}//routes
And "/groups_list" points to this function:
Route createSearchGroupScreen() {
return MaterialPageRoute(
builder: (context) {
//Do we need a DashboardScreen BLOC?
//final storiesBloc = StoriesProvider.of(context);
//storiesBloc.fetchTopIds();
return GroupSearchScreen();
}
);
}
As you can see, the "AppBlocProvider" is only used once.
(I ran into that problem too ;-)
Now to the problem:
When the GroupsListScreen starts rendering the list, the stream/snapshot has no data!
(See "if (!snapshot.hasData)" )
Widget buildList(AppBloc appBloc) {
return StreamBuilder(
stream: appBloc.groupsBlock.groups,
builder: (context, AsyncSnapshot<List<Map<String, dynamic>>>snapshot) {
if (!snapshot.hasData) {
In order to test if all data in the bloc gets lost, I tried not to put the data in the stream directly, but in a member variable (in the bloc!).
In GroupSearchScreen I put the json data in a member variable in the bloc.
Now, just before the GroupsListScreen starts rendering the list, I take the data (json) out of the bloc, put it in the stream, which still resides in the bloc, and everything works fine!
The snapshot has data...
Like this (in the GroupsListScreen):
//Add data to Stream
appBloc.groupsBlock.changeGroupList(appBloc.groupsBlock.groupSearchResult);
Why on earth is the stream "losing" its data on the way from "GroupSearchScreen" to "GroupsListScreen" when the ordinary member variable is not? Both reside in the same bloc!
At the start of the build method of the GroupsListScreen, I have a print statement.
Hence I can see that GroupsListScreen is built twice.
That should be normal, but could that be the reason for not finding data in the stream?
Is the Stream listened on twice?
Widget buildList(AppBloc appBloc) {
return StreamBuilder(
stream: appBloc.groupsBlock.groups,
I tried to explain my problem this way, not providing tons of code.
But I don't know if it's enough to give a hint where I can continue to search...
Update 16.04.2019 - SOLUTION:
I built up my first app using another app seen in a Udemy course...
The most important difference between "his" app and mine is that he creates the Widget that listens to the stream and then adds data to the stream.
I add data to the stream and then navigate to the Widget that shows the data.
Unfortunately I used an RX-Dart "PublishSubject." If you listen to that one you will get all the data put in the stream starting at that time you started listening!
An RX-Dart "BehaviorSubject" however, will also give you the last data, just before you started listening.
And that's the behavior I needed here:
Put data on stream
Create Widget and start listening
I can encourage all Flutter newbies to read both of these very good tutorials:
https://medium.com/flutter-community/reactive-programming-streams-bloc-6f0d2bd2d248
https://www.didierboelens.com/2018/12/reactive-programming---streams---bloc---practical-use-cases/
In the first one, both of the streams mentioned, are explained very well.