How to connect multiple models in connector (ScopedModel) - flutter

I am trying to add fruit item to the cart, but nothing happens
Once I pressed on the 'add fruit' button nothing happens. It supposes to add fruit items in the cart list.
I get an error once trying to access the cart screen by pressing on the cart icon in the app bar after the 'add fruit' button was pressed.
In this way doesn't work properly:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new ScopedModel(
model: ListModel(),
child: ScopedModel(
model: CartModel(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'State Management Scoped Model',
theme: myAppTheme,
initialRoute: '/',
routes: {
'/': (context) => MyList(),
'/cart': (context) => MyCart(),
},
),
),
);
}
}
Another way as well:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new ScopedModel<ListModel>(
model: ListModel(),
child: ScopedModelDescendant<ListModel>(
builder: (context, child, model) => ScopedModel<CartModel>(
model: CartModel(),
),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'State Management Scoped Model',
theme: myAppTheme,
initialRoute: '/',
routes: {
'/': (context) => MyList(),
'/cart': (context) => MyCart(),
},
),
),
);
}
}
With Provider package everything works fine :)

Cannot comment yet.
Try adding the MaterialApp as the direct descendant child of ScopedModel and the use of the ScopedModelDescendant when the changes to the Model actually affect the UI.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new ScopedModel<ListModel>(
model: ListModel(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'State Management Scoped Model',
theme: myAppTheme,
initialRoute: '/',
routes: {
'/': (context) => MyList(),
'/cart': (context) => MyCart(),
},
),
);
}
}
and when the property changes lets say in MyCart widget when you list card items
... widget tree ...
child: ScopedModelDescendant<ListModel>(
builder: (context, child, ScopedModel<CartModel> model){
List cartItems = model.cartItems;
List<Widget> cartItemWidgets=[];
cartItems.forEach((cartItemData){
cartItemWidgets.add(new CartItemWidget(cartItemData));
});
return Column(
children:cartItemWidgets,
);
}
),
...widget tree...
Hope this helps.
Note, ScopedModelDescendant changes every time notifyLsteners() is called.
Doing so on the entire app would be quite expensive I'd think.
Edit:
forgot to add the rebuildOnChange: true property. And also made a mistake.
... widget tree ...
//__YOUR__MODEL__: model that changes
child: ScopedModelDescendant<__YOUR__MODEL__>(
rebuildOnChange: true, // now the widget rebuilds when notifyListeners(); is called inside the __YOUR__MODEL__
builder: (context, child,__YOUR__MODEL__ model){
List cartItems = model.cartItems;
List<Widget> cartItemWidgets=[];
cartItems.forEach((cartItemData){
cartItemWidgets.add(new CartItemWidget(cartItemData));
});
return Column(
children:cartItemWidgets,
);
}
),
...widget tree...
EDIT 2:
From going through the git repository I was able to build an example of what you wanted to do. Here is the link to the GitHub repository. Note: I've initiated an ownership transfer to you. And I'll update the link if you choose to accept. But to also describe the implementation.
You wanted to have two models a ListModel and a CartModel, the list model currently serves as a placeholder for an actual list.
In the CartModel you attached the ListModel.
Whenever you needed the list you got it through the CartModel.
How I changed things?
Split class Fruit into a separate file. // objects.dart
Turned ListModel into abstract ShoppingModel that extends Model
Created a singleton SuperModel that extends Model with ShoppingModel and CartModel. Gave it a private constructor and an instance attribute. There will ever only be one instance of this object.
SuperModel is a mixing it is able to access all the public properties of ShoppingModel and CartModel.
To avoid editor displayed errors a file was added//analysis_options.yaml that suppresses the error. Note this doesn't affect the program, you can find more information about it on the web.
CartModel extends ShoppingModel, now CartModel has access to all the methods of the ShoppingModel it does not have to store the ListModel.
Use ShoppingModel to add attributes that you might have to use across multiple models.
Wrap the app in a ScopeModel, the model attribute is the SuperModel. Since SuperModel is a singleton I used SuperModel.instance, it never has to be instantiated.
Added ScopedModelDescendant everywhere where changes might occur, don't forget the rebuildOnChange: true, property.
I've also given you the owner of the repository.
In general, when using multiple models, use inheritance and mixins if they need to exchange attributes. I always used a top abstract Model descendant class that holds all the data that all of my other Models use. Other models extend this class so they are able to get access to these attributes.
But since we need access to their properties across the app and the abstract Model descendant doesn't know their children, we can create a mixin of all the Model in this case SuperModel.
And because we will ever need a single instance make it a Singleton
class SuperModel extend Model with Model1, Model2{
SuperModel._privateConstructor();
static final SuperModel _instance = SuperModel._privateConstructor();
static SuperModel get instance {
return _instance;
}
}
now we can pass the SuperModel to the ScopedModel.
To enable error free mixins add:
analyzer:
strong-mode: true
language:
enableSuperMixins: true
to the end of the pubspec.yaml file
and a new root file analysis_options.yaml:
analyzer:
errors:
mixin_inherits_from_not_object: ignore
Now this applies to Visual studio code, I don't know if this is handled any differently for Android Studio

Related

initalize static string member in dart with a localized value using flutter_intl localization lib

I'm new to flutter and collecting fragments of code from here and there.
I have a class that holds some data that will be passed later to a widget and generate the UI based on the passed data
class OnBoardingViewModel with ChangeNotifier {
final List<OnBoardingPageContent> onBoardingPages = [
OnBoardingPageContent(
image: ImageManager.onBoardingImage1,
title: StringManager.onBoardingTitle1,// I want the text to be localized
subtitle: StringManager.onBoardingSubTitle1,
),
OnBoardingPageContent(
image: ImageManager.onBoardingImage2,
title: StringManager.onBoardingTitle2,
subtitle: StringManager.onBoardingSubTitle2,
)
];
... some other code
the strings above are hardcoded with specific language I wanted them to be localized
the localization require
S.of(context).yourKey
I thought of passing the key as a string and in UI build (where I will have the context)
to do
`S.of(context)[thePassedKeyStringVar]` // but that is wrong in Dart
I tried to pass the context to my Class OnBoardingViewModel so I could have
class OnBoardingViewModel with ChangeNotifier {
static BuildContext context;
final List<OnBoardingPageContent> onBoardingPages = [
OnBoardingPageContent(
title: S.of(context).onBoardingText1,
but it did not end well with me
here is the entry point of my app
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => OnBoardingViewModel(_),
),
...
],
child: const MyApp(),
),
);
}
any help on how to localize the strings in my static list in the above class?
Short answer : That is not possible with the library you're using (it is a strictly static-only localization library).
Detailed answer :
The way I handle translations when having to manipulate datas in a Controller/Presenter/ViewModel (call it whatever you want), is by returning the key to translate in my model.
You should be able to do what you want with the easy_localization package
/assets/translations/en.json
{[
'my_key': 'The english value',
]}
class AccountController with ChangeNotifier {
AccountObject accountObj = AccountObject();
void updateObject() {
accountObj.title = "my_key";
notifyListeners();
}
}
class AccountWidget extends StatelessWidget {
const AccountWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<AccountController>(
builder: (context, accountController, child) {
return Center(
child: Text(accountController.accountObj.title).tr
);
}
);
}
}
Another good practice is to keep your Widgets outside from your Controllers.
Use your Controllers to build the data and notify the Widget Tree when it should re-build (and keep your Widgets in your Widgets tree) :)
Also, keep in mind that your Context should stay in your Widgets (it'll save you from a lot of errors/weird behaviors :))

is it okay to have two material apps in one flutter?

I added two material app to my app because my futurebuilder needed a context and my provider was not accessible to the other classes i created. is it an acceptable practice???
runApp(
MaterialApp(
title: 'register app',
home: FutureBuilder(
future: Hive.openBox('store'),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError)
return Text(snapshot.error.toString());
else
return MultiProvider(providers: [
ChangeNotifierProvider.value(
value: form_entry(),
)
], child: MyApp());
} else
return Scaffold(
body: Center(
child: Text('error error ooops error'),
));
},
)),
);[enter image description here][1]
// my app class has another material app
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return MaterialApp(
home: home_screen(),
);
}
}
The purpose of a MaterialApp widget is to provide a common theme setting based on Material design and configures the root navigator for all of its children widgets.
In order to avoid conflicting, you should only have 1 MaterialApp. In your case, you can call the openBox() method without using the FutureBuilder by calling it within the main() method:
void main() async {
// Include this line to make sure WidgetsFlutterBinding is initialized, since
// we're using main() asynchronously
WidgetsFlutterBinding.ensureInitialized();
// Open the Hive box here
var box = await Hive.openBox('store');
// Then run the app
runApp(
MaterialApp(
title: 'register app',
home: MultiProvider(providers: [
ChangeNotifierProvider.value(
value: form_entry(),
)
], child: home_screen());
)
);
Small note: When creating new class or method in Dart, best practice is to use CamelCase. So form_entry() should be named FormEntry() for Class name or formEntry() for Method name. Same goes with home_screen(). You can refer to the styling guide here
It's bad to have two MaterialApp() widgets, at least one in other.
I did that by mistake, I thought I do not have one and added an extra one. Then the app randomly crashed on hot restart, sit one whole day to debug everything and haven't found what crashed my app, then I started to refactor code and found I have two MaterialAapp, one in StatelessWidget and one in home widget that was on different file. After removing it my app stopped randomly crashing.
Never use two, atleast not one in other.

Where is the best place to put your ChangeNotifierProvider?

I'm learning the state management approache called Provider & Scope Model.
I made an example and its working fine.
In my example I have a list of entries and a button "+" to add a new entry.
Both views have their own routes, as shown bellow:
static Widget _buildRoute({
#required BuildContext context,
#required String routeName,
Object arguments,
}) {
switch (routeName) {
case Login:
return LoginScreen();
case OccurrenceentriesRoute:
return OccurrenceEntries();
case OccurrenceFormRoute:
Occurrence occurrence = arguments as Occurrence;
return OccurrenceForm(occurrence: occurrence);
default:
throw 'Route $routeName is not defined';
}
}
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => OccurrenceProvider()..loadOccurrences(),
child: MaterialApp(
title: 'Mapify',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
builder: (BuildContext context) => Routes.makeRoute(
context: context,
routeName: settings.name,
arguments: settings.arguments,
),
maintainState: true,
fullscreenDialog: false,
);
},
),
);
}
}
After reading the flutter documentation about the Provider approach I thought that would be a better idea to place the ChangeNotifierProvider as down as possible in the widget tree, as the documentation says:
You don’t want to place ChangeNotifierProvider higher than necessary (because you don’t want to pollute the scope)
My first attempt was to use the ChangeNotifierProviders in my buildRoute, adding only the providers that i needed in each route. I did this, but the notifications made on one route don't affect the others... So, I'm really confuse, where should I place this ChangeNotifierProviders other than in the top of the widget tree?
agree you really shouldn't place your change notifier at the top because that will rebuild the whole app instead
use Multiprovider and wrap it to the very top of you widget tree
like... MaterialApp(
child:MultiProvider(
providers:[],
child:yourWidget()
));
then you can access it by final var _sampleProvider = Provider.of<SomeModel>(context);
I suggest reading more into this if this explanation isn't still clear.

Queue, stream or something else?

I have stateful widgets A, B and C. B and C are both children of A. I have created a queue Q in widget A. Both B and C show the first element of Q, but when 'dismissing' this element in either B or C, both widgets should show the next element of Q.
Currently the problem is that widgets B and C cannot detect changes and I don't think there is a way to listen for queue changes on this Queue implementation: https://api.dartlang.org/stable/2.4.1/dart-collection/Queue-class.html
Should I use a Queue? I have also read about streams, but I am not sure that will work either, because the first element should not necessarily be consumed by B or C. Any ideas?
You could implement the Provider Pattern for State Managment.
Provider Flutter Package
As the documentation says, to expose a variable using provider, wrap any widget into one of the provider widgets from this package and pass it your variable. Then, all descendants of the newly added provider widget can access this variable.
The main idea is to create a class where you will save the state you want to share (in your case it would be the queue). Then, provide access from the widgets to this provider
Example: The following class is the Provider Class. The shared variable would be "_count"
class CounterProvider with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
Then, you instiantate this Provider in the widget of your preference (In your case, A).. so all its descendants (B and C) have access to the provider and its data.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final counter = CounterProvider();
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider( // WIDGET FROM PROVIDER PACKAGE
builder: (context) => CounterProvider(), // REQUIRED LINE
child: MaterialApp(
title: 'My App',
debugShowCheckedModeBanner: false,
initialRoute: '/',
routes: {
'/': (BuildContext context) => HomePage(),
'somepage': (BuildContext context) => SomePage(),
}
),
);
}
}
Now, inside of HomePage():
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final counterProvider = Provider.of<CounterProvider>(context);
// Inject the provider in your widget. From here, you have access to the Counter properties.
return Scaffold(
appBar: AppBar(
title: Text( counterProvider.counter.toString() ), // accessing to the provider!
),
body: Center(
child: Text(counterProvider.counter.toString()), // again
),
);
}
}
Of course you can create your methods you need in the provider so you can change the state in anyway you want.
I hope it helps!
Youtube link in spanish but understable workflow

How to create Login-Wall Views in Flutter

I am working on an app in Flutter and I'm pretty new to it/Dart. I already created the login, signup etc and everything works perfectly fine. Now I want to create a "Login-Wall" Template for every View that needs the user to be logged in. If the user is not logged in, he should be returned to the LoginView, if the api-call is still loading, it should not show anything but a loading screen called LoadingView(). I started by creating a Stateful Widget called AuthorizedLayout:
class AuthorizedLayout extends StatefulWidget {
final Widget view;
AuthorizedLayout({this.view});
_AuthorizedLayoutState createState() => new _AuthorizedLayoutState();
}
The state utilizes a Future Builder as follows:
Widget build(BuildContext context) {
return FutureBuilder<User>(
future: futureToken,
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return NoConnectionView();
case ConnectionState.active:
case ConnectionState.waiting:
return LoadingView();
case ConnectionState.done:
if(snapshot.data != null) {
print("User Data loaded");
return widget.view;
} else
return LoginView();
}
},
);
}
As you can see, it should load the userdata, and when it's finished it should return the view. The futureToken represents the Future that will return the User-Object from the server after an api-request. In any other case it should show the Loading/Error/Login Page.
I'm calling it like this:
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: AuthorizedLayout(
view: DashboardView(),
),
);
}
In the Build method of the Dashboard view I have a "print('Dashboard View');". The problem I have is that in the output the 'Dashboard View' is printed before the 'User Data Loaded'. That means I can't access the loaded user data in that view. This means that this solution does not work the way I intended it to.
Now for my question: Is there any way I can build this "Login-Wall" and pass the user data to every view that is inside the login wall? I hope the code I posted explains the idea I'm trying to go for.
Is there any way I can build this "Login-Wall" and pass the user data to every view that is inside the login wall?
Absolutely! At a basic level, you're talking about state management. Once a user logs into your app, you want to store that user data so that it's accessible to any widget within the widget tree.
State management in Flutter is a hotly-debated topic and while there are a ton of options, there is no defacto state management technique that fits every app. That said, I'd start simple. One of the simplest and most popular options is the scoped_model package.
You can read all of the details here, but the gist is that it provides utilities to pass a data model from a parent widget to its descendants.
First, install the package.
Second, you'll want to create a model that can hold the user data that you want to be accessible to any widget in the tree. Here's a trivial example of what that might look like:
// user_model.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
class UserModel extends Model {
dynamic _userData;
void setUserData(dynamic userData) {
_userData = userData;
}
String getFirstName() {
return _userData['firstName'];
}
static UserModel of(BuildContext context) =>
ScopedModel.of<UserModel>(context);
}
Next, we'll need to make an instance of this UserModel available to all widgets. A contrived way of doing this would be to wrap your entire app in a ScopedModel. Example below:
// main.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'login_view.dart';
import 'user_model.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<UserModel>(
model: UserModel(),
child: MaterialApp(
theme: ThemeData.light(),
home: LoginView(),
),
);
}
}
In the above code, we're wrapping our entire instance of MaterialApp in a ScopedModel<UserModel>, which will give every widget in the application access to the User model.
In your login code, you could then do something like the following when your login button is pressed:
onPressed() async {
// authenticate your user...
var userData = await someApiCall();
// set the user data in our model
UserModel.of(context).setUserData(userData);
// go to the dashboard
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DashboardView(),
),
);
}
Last but not least, you can then access that user data through the UserModel like so:
// dashboard_view.dart
import 'package:flutter/material.dart';
import 'package:scoped_model_example/user_model.dart';
class DashboardView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: Text(
UserModel.of(context).getFirstName(),
),
),
],
);
}
}
Check out the docs on scoped_model for more details. If you need something more advanced, there are a number of other state management patterns in Flutter such as BloC, Redux, Mobx, Provider and more.
So I just got what was happening. I was passing the already-built widget to the AuthorizedView. What I actually had to pass was a Builder instead of a Widget.
class AuthorizedLayout extends StatefulWidget {
final Builder viewBuilder;
AuthorizedLayout({this.viewBuilder});
_AuthorizedLayoutState createState() => new _AuthorizedLayoutState();
}
Calling it like this:
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: AuthorizedLayout(
viewBuilder: Builder(builder: (context) => DashboardLayout()),
),
);
}
Note that I recalled the final variable to viewBuilder instead of view, compared to the example above.
This will actually build the widget AFTER the userdata is loaded.