Flutter Local routes issue - flutter

I have a small problem with a flutter local app. I am trying to have 2 routes depending on device languageCode. When I build the app, that second option are launched. If I save the app, the right route are picked. I think the
final lang = locale.languageCode;
Do not have a value when this is called:
"/newsfeed": lang == "da"
? (BuildContext context) => News()
: (BuildContext context) => NewsEN(),
The print statement I have in the code have the right value, so are a bit lost Here are the code:
class MyApp3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// List all of the app's supported locales here
supportedLocales: [
Locale('en', 'US'),
Locale('da', 'DK'),
],
// These delegates make sure that the localization data for the proper language is loaded
localizationsDelegates: [
// A class which loads the translations from JSON files
AppLocalizations.delegate,
// Built-in localization of basic text for Material widgets
GlobalMaterialLocalizations.delegate,
// Built-in localization for text direction LTR/RTL
GlobalWidgetsLocalizations.delegate,
],
// Returns a locale which will be used by the app
localeResolutionCallback: (locale, supportedLocales) {
// Check if the current device locale is supported
for (var supportedLocale in supportedLocales) {
//print(locale.languageCode);
final lang = locale.languageCode;
print(lang);
if (supportedLocale.languageCode == locale.languageCode &&
supportedLocale.countryCode == locale.countryCode) {
return supportedLocale;
}
}
// If the locale of the device is not supported, use the first one
// from the list (English, in this case).
return supportedLocales.first;
},
home: MyHomePage(),
routes: <String, WidgetBuilder> {
//"/Knowledge_pick": (BuildContext context) => SplashScreen(),
"/calculator_pick": (BuildContext context) => Pickcalculator(),
//"/receipe_pick": (BuildContext context) => Receipemenu(),
"/receipe_pick": (BuildContext context) => Wrapper(),
"/newsfeed": lang == "da"
? (BuildContext context) => News()
: (BuildContext context) => NewsEN(),
Hope someone can help me with this problem.

Honestly I don't see why this wouldn't work but what you should try to do is setup two different routes, one from 'da' and one for 'en'
"/newsfeedDA": (BuildContext context) => News(),
"/newsfeedEN": (BuildContext context) => NewsEN(),
and have your logic for selecting them in the initState or constructor of MyHomePage().

Related

Internationalization with cubit

I'm working on a project and I've been asked to use cubit for internationalization, preferably using the lazy method. For that I have a LocalizationContainer as follows:
class LocalizationContainer extends BlocContainer {
final Widget child;
LocalizationContainer({required this.child});
#override
Widget build(BuildContext context) {
return BlocProvider<CurrentLocaleCubit>(
create: (context) => CurrentLocaleCubit(),
child: child,
);
}
}
class CurrentLocaleCubit extends Cubit<String> {
CurrentLocaleCubit() : super("pt-br");
CurrentLocaleCubit() : super("en-us");
}
In my main file I have the following:
MaterialApp(
title: 'Example',
theme: exampleTheme(context),
debugShowCheckedModeBanner: false,
home: LocalizationContainer(
child: InitialScreenContainer(),
),
);
In this example the child of LocalizationContainer is another container representing the screen. Each screen is structured into container, cubit and view:
The container for screen have the following structure:
class ExampleScreenContainer extends BlocContainer {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ExampleScreenCubit(),
child: I18NLoadingContainer(
language: BlocProvider.of<CurrentLocaleCubit>(context).state,
viewKey : "Example",
creator: (messages) => ExampleScreenView(ExampleScreenViewLazyI18N(messages)),
),
);
}
}
Everytime a new page needs to be opened, I do the following:
Navigator.of(blocContext).push(
MaterialPageRoute(
builder: (context) => BlocProvider.value(
value: BlocProvider.of<CurrentLocaleCubit>(blocContext),
child: NewScreenContainer(),
),
),
);
But whenever I try to hot reload a error pops up. It only works if I do the hot restart. Does somebody know how to solve this problem, or this internationalization method is wrong?
I did not really get the problem (I think if you put the error that's pop up I can help you more), but this way I do localizations (I use bloc).
first off all you need to add BlocProvider above MaterialApp so he become ancestor to every widget in context tree, so when ever you called BlocProvider.of(context)
you can get the instance of this bloc where ever you are in the tree (no need to do BlocProvider above every screen you are pushing).
now when ever you change language of your app and yield the new state the BlocBuilder will rebuild the whole app with the new language.
class AppProvider extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(providers: [
BlocProvider<AppBloc>(
create: (_) => sl<AppBloc>()
//get app default language
..add(const AppEvent.initialEvent()),
),
], child: App());
}
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<AppBloc, AppState>(
builder: (context, state) => MaterialApp(
debugShowCheckedModeBanner: false,
home: SplashScreen()),
locale: state.language == AppLanguageKeys.AR
? const Locale('ar', '')
: const Locale('en', ''),
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''), // English
const Locale('ar', ''), // Arabic
],
),
);
}
}

flutter: Navigation routes not working on authentication

I am new to flutter and trying to get this to work. I have a Welcome() page, which has two buttons (SignUp and SignIn), which take you to SignUp() and SignIn() pages. After successfully authenticating, the app should navigate to Home(), but it does not. Neither does it go back to Welcome() page if user logs out. Also, if the app is started and user was already logged in, it doesn't automatically go to Home(), it stays at Welcome(). What am I doing wrong here?
I am using a StreamProvider like so
if (snapshot.connectionState == ConnectionState.done) {
return StreamProvider<User>.value(
value: AuthService().user, child: Wrapper());
}
And my wrapper looks like this:
class Wrapper extends StatelessWidget {
const Wrapper({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user != null)
print(user.id);
else
print("User is null");
return MaterialApp(initialRoute: '/', routes: {
'/': (context) => user == null ? Welcome() : Home(),
'/signup': (context) => SignUp(),
'/signin': (context) => SignIn()
'/profile': (context) => Profile(),
'/edit': (context) => Edit()
});
}
This prints the user ID, which means the user is not null, but it does not navigate to Home().
I'm not dictating you what to do but i will just show you how i usually do.
The concept is simple. When the user successfull login or signup, you must save this information in a shared_preference. It may be like auth : true.
You must also wait on the app launching process to see if the user already login or not and based on that, you can navigate to another screen.
To check at startup if user is authenticated, you must read the auth property you previously added in shared_preference. Look at this code :
void main() async {
var mapp;
var routes = <String, WidgetBuilder>{
'/initialize': (BuildContext context) => Initialize(),
'/register': (BuildContext context) => Register(),
'/home': (BuildContext context) => Home(),
};
print("Initializing.");
WidgetsFlutterBinding.ensureInitialized();
await SharedPreferencesClass.restore("initialized").then((value) {
if (value) {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Home(),
);
} else {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Initialize(),
);
}
});
print("Done.");
runApp(mapp);
}
This code is so self explanatory. You can adapt it to your edge.
To navigate to a new screen it is simple, just use this code :
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);

Can't create new Flutter Provider

Hi I am trying to add a new Provider on the root level of my App. However, the create function of the new Provider (In this case more easily represented by the Provider of type int) doens't get called as I can see in my Console.
class MyApp extends StatelessWidget {
final List<SingleChildWidget> providers = [
Provider<DynamicLinkService>(
create: (context) {
print("Dynamic Link Service Provider gets built");
return DynamicLinkService();
},
),
Provider<int>(create: (context) {
print("Int Provider gets built");
return 1;
}),
];
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: providers,
builder: (ctx, _) {
return MaterialApp(
theme: themeData(context),
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
onGenerateRoute: (settings) {
return getPageRoute(settings);
},
home: StartUpView(),
navigatorKey: locator<NavigationService>().navigatorKey,
);
},
);
}
}
Console Output:
Dynamic Link Service Provider gets built

Why does flutter localization not work properly?

I want to add the tr tag to the Flutter localization section. I am
getting an error although there is support for Tr. The error is as
follows; error: The element type 'Locale (where Locale is defined in
C:\Users\StatTark\AppData\Roaming\Pub\Cache\hosted\pub.dartlang.org\intl-0.16.1\lib\src\locale.dart)'
can't be assigned to the list type 'Locale (where Locale is defined in
C:\flutter\bin\cache\pkg\sky_engine\lib\ui\window.dart)'.
error: Abstract classes can't be instantiated.
(instantiate_abstract_class at [ajanda] lib\pages\mainmenu.dart:36)
I do not understand if I am making a mistake in use, I will be glad if you help.
class MainMenu extends StatelessWidget {
MainMenu({Key key}) : super(key: key);
final _sdb = SettingsDbHelper();
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _sdb.getSettings(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: [Locale('en','US'),Locale('tr','')], //The error is here
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Text(proTranslate["Yükleniyor....."][Language.languageIndex]),
),
),
);
} else {
Language.languageIndex = snapshot.data[0].language;
return DynamicTheme(
defaultBrightness: Brightness.light,
data: (brightness) => ThemeData(
brightness: brightness,
fontFamily: snapshot.data[0].fontName,
floatingActionButtonTheme: FloatingActionButtonThemeData(
foregroundColor: Colors.green,
),
),
themedWidgetBuilder: (context, theme) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: [Locale('en','US'),Locale('tr','')],
debugShowCheckedModeBanner: false,
theme: theme,
home: MainMenuBody(
warning: snapshot.data[0].warning,
),
// navigatorKey: navigatorKey,
);
});
}
},
);
}
}
class MainMenuBody extends StatefulWidget {....}
I'm coming a bit late but I just fixed this same issue I had.
I'm pretty sure your file is importing intl/locale.dart instead of flutter/material.dart as both define a Local type.
To fix it, just replace your import at the top of the file from:
import 'package:intl/locale.dart';
to
import 'package:flutter/material.dart';
and you should be OK.
Few suggestions you can try/test, see below.
Replace [Locale('en','US'),Locale('tr','')], with [Locale('en'),Locale('tr')],
init list of supported locales first and use it accordingly.
// init before build
final List<Locale> appSupportedLocales = [
Locale('en'),
Locale('tr')
];
// ...
// then use it like this
supportedLocales: appSupportedLocales,

How can I stop my change notifier provider from rebuilding my parent material app when I am rendering my child material app?

I have a app class that returns a MaterialApp() which has it's home set to TheSplashPage(). This app listens to the preferences notifier if any preferences are changed.
Then in TheSplashPage() I wait for some conditionals to be true and if they are I show them my nested material app.
Side Note: I use a material app here because it seems more logical since it has routes that the parent material app shouldn't have. And also once the user is unauthenticated or gets disconnected I want the entire nested app to shut down and show another page. This works great!
But my problem is the following. Both apps listen to ThePreferencesProvider() so when the theme changes they both get notified and rebuild. But this is a problem because whenever the parent material app rebuilds, it returns the splash page. So now I am back on TheSplashPage() whenever I change a setting on TheSettingsPage().
So my question is how can I stop my application from going back to the TheSplashPage() whenever I change a setting?
Main.dart
void main() {
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return MultiProvider(
providers: [
ChangeNotifierProvider<PreferencesProvider>(create: (_) => PreferencesProvider()),
ChangeNotifierProvider<ConnectionProvider>(
create: (_) => ConnectionProvider(),
),
ChangeNotifierProvider<AuthenticationProvider>(create: (_) => AuthenticationProvider()),
],
child: Consumer<PreferencesProvider>(builder: (context, preferences, _) {
return MaterialApp(
home: TheSplashPage(),
theme: preferences.isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
debugShowCheckedModeBanner: false,
);
}),
);
}
}
TheSplashPage.dart
class TheSplashPage extends StatelessWidget {
static const int fakeDelayInSeconds = 2;
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.delayed(new Duration(seconds: fakeDelayInSeconds)),
builder: (context, delaySnapshot) {
return Consumer<ConnectionProvider>(
builder: (BuildContext context, ConnectionProvider connectionProvider, _) {
if (delaySnapshot.connectionState != ConnectionState.done ||
connectionProvider.state == ConnectionStatus.uninitialized) return _buildTheSplashPage(context);
if (connectionProvider.state == ConnectionStatus.none) return TheDisconnectedPage();
return Consumer<AuthenticationProvider>(
builder: (BuildContext context, AuthenticationProvider authenticationProvider, _) {
switch (authenticationProvider.status) {
case AuthenticationStatus.unauthenticated:
return TheRegisterPage();
case AuthenticationStatus.authenticating:
return TheLoadingPage();
case AuthenticationStatus.authenticated:
return MultiProvider(
providers: [
Provider<DatabaseProvider>(create: (_) => DatabaseProvider()),
],
child: Consumer<PreferencesProvider>(
builder: (context, preferences, _) => MaterialApp(
home: TheGroupManagementPage(),
routes: <String, WidgetBuilder>{
TheGroupManagementPage.routeName: (BuildContext context) => TheGroupManagementPage(),
TheGroupCreationPage.routeName: (BuildContext context) => TheGroupCreationPage(),
TheGroupPage.routeName: (BuildContext context) => TheGroupPage(),
TheSettingsPage.routeName: (BuildContext context) => TheSettingsPage(),
TheProfilePage.routeName: (BuildContext context) => TheProfilePage(),
TheContactsPage.routeName: (BuildContext context) => TheContactsPage(),
},
theme: preferences.isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
debugShowCheckedModeBanner: false,
)),
);
}
});
});
});
}
TheSettingsPage.dart
Switch(
value: preferences.isDarkMode,
onChanged: (isDarkmode) => preferences.isDarkMode = isDarkmode,
),
You fell for the XY problem
The real problem here is not "my widget rebuilds too often", but "when my widget rebuild, my app returns to the splash page".
The solution is not to prevent rebuilds, but instead to change your build method such that it fixes the issue, which is something that I detailed previously here: How to deal with unwanted widget build?
You fell for the same issue as in the cross-linked question: You mis-used FutureBuilder.
DON'T:
#override
Widget build(BuildContext context) {
return FutureBuilder(
// BAD: will recreate the future when the widget rebuild
future: Future.delayed(new Duration(seconds: fakeDelayInSeconds)),
...
);
}
DO:
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
// Cache the future in a StatefulWidget so that it is created only once
final fakeDelayInSeconds = Future<void>.delayed(const Duration(seconds: 2));
#override
Widget build(BuildContext context) {
return FutureBuilder(
// Rebuilding the widget no longer recreates the future
future: fakeDelayInSeconds,
...
);
}
}
When using Consumer, you are forcing the widget to rebuild every time you notify listeners.
To avoid such behaviour, you can use Provider.of as stated in ian villamia's answer, as it can be used wherever you need it, and only where you need it.
The changes in your code to use Provider.of would be removing the consumer and adding Provider.of when resolving the theme as follows:
theme: Provider.of<PreferencesProvider>(context).isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
HOWEVER if you want to keep using Consumer, you can do something else:
The child property on the Consumer widget is a child that is not rebuilt. You can use this to set the TheSpashScreen there, and pass it to the materialApp through the builder.
TL:DR
Use Provider.of if you need only to tap into one variable for simplicity.
Use Consumer with its child property as the child doesn't rebuild. <= Better performance
Using Provider.of
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return MultiProvider(
providers: [
ChangeNotifierProvider<PreferencesProvider>(create: (_) => PreferencesProvider()),
ChangeNotifierProvider<ConnectionProvider>(
create: (_) => ConnectionProvider(),
),
ChangeNotifierProvider<AuthenticationProvider>(create: (_) => AuthenticationProvider()),
],
child: Builder(
builder: (ctx) {
return MaterialApp(
home: TheSpashPage(),
theme: Provider.of<PreferencesProvider>(ctx).isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
);
}),
);
}
}
Using Consumer
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return MultiProvider(
providers: [
ChangeNotifierProvider<PreferencesProvider>(create: (_) => PreferencesProvider()),
ChangeNotifierProvider<ConnectionProvider>(
create: (_) => ConnectionProvider(),
),
ChangeNotifierProvider<AuthenticationProvider>(create: (_) => AuthenticationProvider()),
],
child: Consumer<PreferencesProvider>(
child: TheSpashPage(),
builder: (context, preferences, child) {
return MaterialApp(
home: child,
theme: preferences.isDarkMode ? DarkTheme.themeData : LightTheme.themeData,
debugShowCheckedModeBanner: false,
);
}),
);
}
}
I hope this is helpful for you!
basically there's 2 ways in using a provider
one it the current one you're using which is the consumer type,
is using the instance of a provider
final _preferencesProvider= Provider.of<PreferencesProvider>(context, listen: false);
you can toggle the "listen:true" if you want the widget to rebuild when notifyListeners() are called... false if otherwise
also just use _preferencesProvider.someValue like any other instance