is it okay to have two material apps in one flutter? - 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.

Related

Flutter 2.10: hot reload displaying blank screen

I upgraded to flutter 2.10 for a new projet, I setup my project like i usually do (injectable, getIt... ect).
what I'm facing right now, is whenever I do a hot reload (ctrl+s), the whole application rebuilds but it displays normally for a fraction of a second then it displays a blank screen.
like so:
also when looking at the inspector after the unexpected blank screen I only find:
knowing that normally it displays the widget's tree (login screen...).
the main function:
void main() async {
setupLogging();
setPathUrlStrategy();
final LocalizationDelegate delegate = await createDelegate();
BlocOverrides.runZoned(
() => runApp(LocalizedApp(delegate, Application())),
blocObserver: ApplicationObserver(),
);
}
the Application Class:
class Application extends StatelessWidget {
Application({Key? key}) : super(key: key);
final initializer = initialize();
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: initializer,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return Container(
child: const Center(
child: CircularProgressIndicator(),
),
);
case ConnectionState.done:
log("**************************** or here");
final delegate = getDelegate(context);
final AppRouter router = AppRouter(authGuard: getIt<AuthGuard>());
return LocalizationProvider(
state: LocalizationProvider.of(context).state,
child: MaterialApp.router(
routeInformationParser: router.defaultRouteParser(),
routerDelegate: router.delegate(),
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
delegate,
],
supportedLocales: delegate.supportedLocales,
locale: delegate.currentLocale,
theme: ApplicationTheme.lightTheme,
// darkTheme: ApplicationTheme.darkTheme , todo
),
);
}
});
}
static Future initialize() async {
await configureDependencies();
}
}
the logs after hot reload display:
[log] **************************** or here // at the application state done
[log] ****************************** inside the login? // at the login screen build
[log] **************************** or here // back at the application state done, but no
login screen after this...
What may be the cause of thise issue? any hints please?
The issue is not related to flutter 2.10, it is because of where i'm creating the application router.
since i'm creating the application router inside the FutureBuilder, each time i hot reload, a new router gets created and weird things happen.
I fixed it by delegating the creation of my router to getIt as a singleton, and getit after the FutureBuilder is done, since it's a singleton, it won't be recreated each time.
final AppRouter router = getIt<AppRouter>();

how do i use provider in this situation

I want to create a change app theme mode and I saw a way of creating it with Provider but I'm new to Provider. For Example, I want to add some codes like this
(the highlighted code)
in my main which consists of many routes
You want to change the theme of the app, then you need to move provider up so it can cover the widget (App in this case) state,
You could do something like this in your main method :
runApp(ChangeNotifierProvider(
create: (context) => ThemeProvider(),
child:MyApp()
);
now in the case of children you could simply call provider in the build method like this
Widget build(){
var themeProvider = Provider.of<ThemeProvider>(context);
}
or you could use the consumer widget
Consumer<ThemeProvider>(
builder: (context, provider, child) {
//return something
}
)
I suggest you to move your ChangeNotifierProvider to your runApp() method
runApp(
ChangeNotifierProvider<ThemeProvider>(
create: (_) => ThemeProvider(),
child: MyApp(),
),
),
Where your MyApp() is just all of your app extracted to its own widget.
Then you can actually easily access it as you wish with a Consumer widget on your build method.
return Consumer<ThemeProvider>(
builder: (BuildContext context, ThemeProvider provider, _) {
return MaterialApp(
theme: provider.myTheme,
...
);
}
)

How to connect multiple models in connector (ScopedModel)

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

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.

Opening keyboard causes stateful widgets to be re-initialized

I am using Flutter 1.2.1 in the Stable branch. To illustrate my problem imagine I have pages A and B. A navigates to B using Navigator.push and B navigates back to A using Navigator.pop. Both are stateful widgets.
When I navigate from A to B and then pop back to A everything is fine and A keeps its state. However, if I navigate from A to B, tap a textfield in B opening the keyboard, then close the keyboard and pop back to A, A's entire state is refreshed and the initState() method for A is called again. I verified this by using print statements.
This only happens when I open the keyboard before popping back to A. If I navigate to B, then immediately navigate back to A without interacting with anything then A keeps its state and is not re-initialized.
From my understanding the build method is called all the time but initState() should not get called like this. Does anyone know what is going on?
After much trial and error I determined the problem. I forgot that I had setup a FutureBuilder for the / route in my MaterialApp widget. I was passing a function call that returns a future to the future parameter of the FutureBuilder constructor rather than a variable pointing to a future.
So every time the routes got updated a brand new future was being created. Doing the function call outside of the MaterialApp constructor and storing the resulting future in a variable, then passing that to the FutureBuilder did the trick.
It doesn't seem like this would be connected to the weird behavior I was getting when a keyboard opened, but it was definitely the cause. See below for what I mean.
Code with a bug:
return MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
buttonColor: Colors.lightBlue,
),
routes: {
'/': (context) => FutureBuilder<void>(
future: futureFun(), //Bug! I'm passing a function that returns a future when called. So a new future is returned each time
builder: (context, snapshot) {
...
}
...
}
...
}
Fixed Code:
final futureVar = futureFun(); //calling the function here instead and storing its future in a variable
return MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
buttonColor: Colors.lightBlue,
),
routes: {
'/': (context) => FutureBuilder<void>(
future: futureVar, //Fixed! Passing the reference to the future rather than the function call
builder: (context, snapshot) {
...
}
...
}
...
}
did you use AutomaticKeepAliveClientMixin in "A" widget ?
if you don't , see this https://stackoverflow.com/a/51738269/3542938
if you already use it , please give us a code that we can test it directly into "main.dart" to help you
Yup, happened to me, perhaps it's much better to wrap the FutureBuilder itu a PageWidget, and make it singleton
return MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
buttonColor: Colors.lightBlue,
),
routes: {
'/': (context) => PageWidget() // wrap it by PageWidget
...
}
...
}
class PageWidget extends StatelessWidget {
static final _instance = PageWidget._internal(); // hold instance
PageWidget._internal(); // internal consturctor
factory PageWidget() {
return _instance; // make it singleton
}
#override
Widget build(BuildContext context) {
return FutureBuilder<void>( ... );
}
}
I got a solution, I was initialising variables in the constructor of the superclass. I removed it and worked!
I just removed the FutureBuilder from the home of MaterialApp and changed the MyApp into a Stateful widget and fetched the requisite info in the initState and called setState in the .then(); of the future and instead of passing multiple conditions in the home of MaterialApp, I moved those conditions to a separate Stateful widget and the issue got resolved.
initState:
#override
void initState() {
// TODO: implement initState
// isSignedIn = SharedPrefHelper.getIsSignedIn();
getIsSignedInFromSharedPreference().then((value) {
setState(() {
isSignedInFromSharedPref = value ?? false;
if (isSignedInFromSharedPref) {
merchantKey = LocalDatabase.getMerchantKeyWithoutAsync();
}
isLoadingSharedPrefValue = false;
});
});
super.initState();
}
Future<bool?> getIsSignedInFromSharedPreference() async {
return SharedPrefHelper.getIsSignedIn();
}
MaterialApp (now):
MaterialApp(
title: 'Loveeatry POS',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Home(
isLoadingSharedPrefValue: isLoadingSharedPrefValue,
isSignedInFromSharedPref: isSignedInFromSharedPref,
merchantKey: merchantKey,
),
),
Home:
class Home extends StatelessWidget {
final bool isLoadingSharedPrefValue;
final bool isSignedInFromSharedPref;
final String merchantKey;
const Home({
Key? key,
required this.isLoadingSharedPrefValue,
required this.isSignedInFromSharedPref,
required this.merchantKey,
}) : super(key: key);
#override
Widget build(BuildContext context) {
if (!isLoadingSharedPrefValue) {
if (isSignedInFromSharedPref) {
return const Homepage(
shouldLoadEverything: true,
);
} else if (merchantKey.isNotEmpty) {
return LoginPage(merchantKey: merchantKey);
} else {
return const AddMerchantKeyPage();
}
} else {
return loading(context);
}
}
}
P.S.: If you need any more info, please leave a comment.