Flutter app reload when i change the language ( easy_localization ) - flutter

I'm new to flutter and Dart currently, I'm using flutter 2.03 and trying to build a multi-language app using the easy_localization package (3.0.0). At first, everything was alright when I try to change the app language from the setting page or the first page which is shown one time, the app translates the content and stays on the same page but yesterday the app started reloading when I change the app locale :
onChanged: (newValue) async {
if (newValue == 'English') {
await context.setLocale(Locale('en'));
} else if (newValue == 'Français') {
await context.setLocale(Locale('fr'));
} else if (newValue == 'العربية') {
await context.setLocale(Locale('ar'));
}
},
All I want is to make the app make hot reload and translate the page and stay on the same screen without reloading the whole app and back to Home Screen.
Main.dart
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:sg_user_dash/screens/homescreen.dart';
import 'package:sg_user_dash/screens/language.dart';
import 'package:shared_preferences/shared_preferences.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
runApp(EasyLocalization(
supportedLocales: [Locale('en'), Locale('fr'), Locale('ar')],
path: 'assets/translations',
fallbackLocale: Locale('en'),
child: MyApp()));
}
Future<String> nextdisplay() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool _seen = (prefs.getBool('seen') ?? false);
if (_seen) {
return "Homepage";
} else {
await prefs.setBool('seen', true);
return "walkthrough";
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Smart Government',
theme: ThemeData(),
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
home: FutureBuilder(
future: nextdisplay(),
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
if (snapshot.data == "walkthrough") {
return Language();
} else if (snapshot.data == "Homepage") {
return HomeScreen();
} else {
return Language();
}
}
return Center(
child: CupertinoActivityIndicator(),
);
}));
}
}
Thank you <3

I have a solution if someone needs to change locale on Android or IOs automatically from device settings.
Since changing the language usually requires changing the app with focus, your app will pause and resume when you return to it.
For this reason I have used the app life cycle as a listener to change the language, giving me good results.
(Check the docs for more info about App Lifecycle: Android | IOs).
Next I will show a simple example that could be useful to you.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
runApp(
EasyLocalization(
supportedLocales: const [
Locale('es'),
Locale('en'),
Locale('fr'),
Locale('pt'),
Locale('it'),
Locale('de'),
],
fallbackLocale: const Locale('en'),
path: 'res/assets/langs',
useOnlyLangCode: true,
child: const MyApp(),
),
);
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(
LifecycleEventHandler(
resumeCallBack: () {
final locale = context.locale.toString();
// Get lang code only if not using country code.
final platformLocale = Platform.localeName.split("_")[0];
if(platformLocale != locale) {
// Select device lang or English if not supported.
final supportedLocale = getSuppLangOrEn(platformLocale);
context.setLocale(Locale(supportedLocale));
}
},
),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
title: 'Localized App',
theme: theme,
home: const HomeScreen(),
initialRoute: HomeScreen.routeName,
routes: routes,
),
);
}
}
And the LifecycleEventHandler would look like this:
class LifecycleEventHandler extends WidgetsBindingObserver {
final VoidCallback? resumeCallBack;
final VoidCallback? suspendingCallBack;
LifecycleEventHandler({
this.resumeCallBack,
this.suspendingCallBack,
});
#override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
switch (state) {
case AppLifecycleState.resumed:
if (resumeCallBack != null) {
resumeCallBack!();
}
break;
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
case AppLifecycleState.detached:
if (suspendingCallBack != null) {
suspendingCallBack!();
}
break;
}
}
}
Credits: LifecycleEventHandler code was taken from here.

easy_localization save the language when its change on runtime you don't need to save it again.
I have a video on how to use easy_localization I think it can be a help
https://www.youtube.com/watch?v=LS8KFYsR244

so sample add setState((){});
thats work for me, But I dont know why :)
onChanged: (newValue) async {
if (newValue == 'English') {
await context.setLocale(Locale('en'));
} else if (newValue == 'Français') {
await context.setLocale(Locale('fr'));
} else if (newValue == 'العربية') {
await context.setLocale(Locale('ar'));
}
setState((){});
},

Related

Flutter Auto Login Remember Me function using Provider

I'm trying to do autologin function for my flutter web app using provider. I couldn't find any articles or videos about this and I wonder how it should implemented.
What I'm trying to do is check the shared preferences before launching site. App uses URL routing so I have to do the checking whatever url the user goes.
Here is my main code.
void main() {
setUrlStrategy(PathUrlStrategy());
setupLocator();
runApp(MultiProvider(
providers: [ChangeNotifierProvider(create: (_) => AppStateProvider())],
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
final RouteInformationProvider? routeInformationProvider;
const MyApp({Key? key, this.routeInformationProvider}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Title',
theme: ThemeData(
primarySwatch: Colors.grey,
fontFamily: 'Poppins'
),
// home: HomeView(),
routerDelegate: RoutemasterDelegate(
routesBuilder: (context) {
return buildRouteMap(context);
},
),
routeInformationParser: RoutemasterParser(),
routeInformationProvider: routeInformationProvider,
);
}
}
And here is my AppStateProvider class
class AppStateProvider extends ChangeNotifier {
bool loading = false;
AppStateModel? appState;
checkCaches() async {
loading = true;
final prefs = await SharedPreferences.getInstance();
final String? acToken = prefs.getString('accessToken');
if (acToken != null) {
appState?.isLoggedIn = true;
appState?.accessToken = acToken;
final String? comToken = prefs.getString('companyToken');
appState?.companyToken = comToken;
loading = false;
notifyListeners();
} else {
appState?.isLoggedIn = false;
notifyListeners();
}
}
storeCaches(String acToken, String? comToken) async {
loading = true;
final prefs = await SharedPreferences.getInstance();
prefs.setString('accessToken', acToken);
prefs.setString('companyToken', comToken!);
appState?.isLoggedIn = true;
appState?.accessToken = acToken;
appState?.companyToken = comToken;
loading = false;
notifyListeners();
}
}
Any ideas about the implementation?
I thought Consumer might work for you
like this
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: WebLocalStorage().userInfoNotif,
builder: (BuildContext context, User value, Widget child) {
return value == null
? HBLoginPageNew()
: HBHomePage(
user: WebLocalStorage.instance.user,
);
},
);}
this is my autologin code. I'm using ValueNotifier. Consumer works with the ChangeNotifierProvider.

FlutterNativeSplash.removeAfter(initialisation) renders next screen before initialisation completes

I am using flutter_native_splash package and shared_preferneces to store my app data. I have the following code in my main.dart file.
import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:location/location.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'home_management.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.removeAfter(initialization);
runApp(const MyApp());
}
void initialization(BuildContext context) async {
// Initialise shared preferences
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
// Initialise user location and store it in shared preferences
Location _location = Location();
bool? _serviceEnabled;
PermissionStatus? _permissionGranted;
LocationData? _locationData;
_serviceEnabled = await _location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await _location.requestService();
}
_permissionGranted = await _location.hasPermission();
if (_permissionGranted == PermissionStatus.denied) {
_permissionGranted = await _location.requestPermission();
}
_locationData = await _location.getLocation();
sharedPreferences.setDouble('latitude', _locationData.latitude!);
sharedPreferences.setDouble('longitude', _locationData.longitude!);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(brightness: Brightness.light),
darkTheme: ThemeData(brightness: Brightness.dark),
themeMode: ThemeMode.dark,
home: const HomeManagement(),
);
}
}
I am using HomeManagement to manage my Pages with a bottom navigation bar, and the first page to load is RestaurantsMap() which looks as below.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class RestaurantsMap extends StatefulWidget {
const RestaurantsMap({Key? key}) : super(key: key);
#override
State<RestaurantsMap> createState() => _RestaurantsMapState();
}
class _RestaurantsMapState extends State<RestaurantsMap> {
late Future<SharedPreferences> sharedPreferences;
#override
void initState() {
sharedPreferences = SharedPreferences.getInstance();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Restaurants Map'),
),
body: FutureBuilder(
future: sharedPreferences,
builder: (BuildContext context,
AsyncSnapshot<SharedPreferences> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
print(snapshot.data!.getDouble('latitude'));
return const Center(
child: Text('Start building something awesome! 💪🏻'),
);
} else {
return Container();
}
}),
);
}
}
Whenever I am accessing the latitude in RestaurantsMap inside the FutureBuilder, I am getting a null printed by the following line of code:
print(snapshot.data!.getDouble('latitude'));
Using print statements inside the initialization() function after sharedPreferences.setDouble returns the data, so the only logical explanation is that I am accessing the getDouble('latitude') before it is getting set.
Any observations/solutions would be helpful.
For future viewers, if anyone faces the same issue, just update to the latest version for flutter_native_splash. An update has been released that gives more flexibility to make a call to remove the splash screen.
Here is the new readme - https://pub.dev/packages/flutter_native_splash#3-set-up-app-initialization-optional

How save and write data using GetX storage?

First I created the GetxController class
final languageController = GetStorage();
var myLocal = [];
void saveLocale(List list) {
languageController.write('savedLocale', list);
}
#override
void onInit() {
List<dynamic>? savedLocale = languageController.read('savedLocale');
if (savedLocale != null) {
myLocal = savedLocale;
}
super.onInit();
}
}
Then I initialized GetStorage in main.dart
final myLocal = LanguageController().myLocal;
void main() async {
print(myLocal);
await GetStorage.init();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
translations: LocaleString(),
locale: myLocal.isNotEmpty
? Locale(myLocal[0], myLocal[1])
: Locale('en', 'US'),
debugShowCheckedModeBanner: false,
home: HomeScreen(),
);
}
}
And then in the dialog after setting the locale I writes it in storage
Future<dynamic> myMaterialDialog(BuildContext context) {
final LanguageController languageController = Get.find();
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(chooseLanguage.tr),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () {
Get.back();
Get.updateLocale(Locale('en', 'US'));
languageController.saveLocale(['en', 'US']);
},
child: Text(englishLanguage.tr),
),
TextButton(
onPressed: () {
Get.back();
Get.updateLocale(Locale('ru', 'RU'));
languageController.saveLocale(['ru', 'RU']);
},
child: Text(russianLanguage.tr),
),
],
),
);
});
}
And it's not working, every time I restarted my app it's shows 1 what myLocale is empty
To check if saveLocale() method is working, I created printSavedLocale() method
void printSavedLocale() {
print(languageController.read('savedLocale'));
}
and put it to dialoge button after saveLocale() and it's printing my saved locale, but after restarting saved locale is null
use this static methods. put them anywhere in your project:
void setData(String key, dynamic value) => GetStorage().write(key, value);
int? getInt(String key) => GetStorage().read(key);
String? getString(String key) => GetStorage().read(key);
bool? getBool(String key) => GetStorage().read(key);
double? getDouble(String key) => GetStorage().read(key);
dynamic getData(String key) => GetStorage().read(key);
void clearData() async => GetStorage().erase();
I can solove this by reading from the storage directly from main.dart
final LanguageController languageController = Get.put(LanguageController());
final myLocal = LanguageController().readSavedLocale();
void main() async {
await GetStorage.init();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
translations: LocaleString(),
locale: myLocal.isNotEmpty
? Locale(myLocal[0], myLocal[1])
: Locale('en', 'US'),
debugShowCheckedModeBanner: false,
home: HomeScreen(),
);
}
}
And readSavedLocale() method is
List readSavedLocale() {
var savedLocale = languageController.read('savedLocale');
return savedLocale;
}
if you still needs this , I use my app differently but I just made it work he it my main file (minus what you don' need)
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await GetStorage.init();
await firebaseInitialization.then((value) {
Get.put(HomeController());
});
runApp(Start());
}
class Start extends StatelessWidget {
Start({
Key? key,
}) : super(key: key);
final storage = GetStorage();
#override
Widget build(BuildContext context) {
Get.put(HomeController());
print(storage.read('langCode'));
print(storage.read('countryCode'));
return GetMaterialApp(
translations: LocaleString(),
fallbackLocale: const Locale('en', 'US'),
locale: storage.read('langCode') != null
? Locale(storage.read('langCode'), storage.read('countryCode'))
: const Locale('ar', 'MA'),
title: 'title'.tr,
}));
}
}
i have a button on my drawer that switches between arabic and english, you can put it wherever you want, you just need to have the widget
class Page extends GetView<HomeController>
which gives you the value 'controller' to represent the controller responsible for the language.
and this is the button responsible for the switch:
SizedBox(
height: 70,
child: OutlinedButton(
child: ListTile(
title: Text(
'language'.tr,
style: Theme.of(context).textTheme.headline6,
textDirection: TextDirection.rtl,
),
leading: const Icon(Icons.language),
),
onPressed: () {
controller.switchLang();
},
)),
and here is my homeController which is responsible for the locale:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
class HomeController extends GetxController {
static HomeController instance = Get.find();
final storage = GetStorage();
var ar = const Locale('ar', 'MA');
var us = const Locale('en', 'US');
switchLang() {
if (Get.locale == us) {
Get.updateLocale(ar);
storage.write('langCode', 'ar');
storage.write('countryCode', 'MA');
} else {
Get.updateLocale(us);
storage.write('langCode', 'en');
storage.write('countryCode', 'US');
}
update();
}
}
in your case if you have multiple locales , just change my switchlang function to handle multiple locales, you can do that easily with a switch case

Flutter login control in splash screen

I want to make a small login application. When entering the application, I want to inquire whether the user has a token code or not on the splash screen. How can do this? thank you for help.
main.dart file
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SplashScreen(),
);
}
}
My splash screen.
I want to know if the user has a token or not
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
loginControl();
}
// ignore: missing_return
Future<bool> loginControl() async {
bool status = AuthController.isLoginUser() as bool;
print(status);
if (status) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (BuildContext context) => HomeScreen()));
} else {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (BuildContext context) => LoginScreen()));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('welcome my app'),
),
);
}
}
my auth controller like this;
class AuthController {
static Future<bool> isLoginUser() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
String token = sharedPreferences.getString("token");
if (token == null) {
return false;
} else {
return true;
}
}
}
Your isLoginUser is actually returning a Future<bool> means that it returns a Future that will later resolve to a bool value.
So, when you use it like this in your loginControl,
bool status = AuthController.isLoginUser() as bool;
AuthController.isLoginUser() return Future<bool> and it can't be directly converted to a bool using as bool.
Instead you should await that Future to resolve, like this.
bool status = await AuthController.isLoginUser(); // This will work.
Now, your code will pause at this line, until it gets a return value from isLoginUser and then resume to next line with status being an actual bool value. i.e., true or false.

Flutter App with Firebase Hosting including Authentication and Database

I have created a small Flutter app, which includes firebase auth and database. I want to deploy it with firebase hosting and did all the necessary steps, but it is still not working. The webside is just empty or MAYBE waiting for something (not sure about that).
firebase login
firebase init
added "site": "xxxxxxxxxxx-d1d99" to my firebase.json file
added js sdks to index.html:
<script src="/__/firebase/8.3.2/firebase-auth.js"></script>
<script src="/__/firebase/8.3.2/firebase-database.js"></script>
firebase deploy
Also I have chosen the 'web' directory instead of 'build/web' as my public directory, because my flutter project creates the flutter create web there.
My main.dart looks like the following. Except from the MaterialApp(), it is completely the same as suggested by flutter/firebase on how to do it.
main.dart
import 'package:faschingsplaner/views/auth/authentication.dart';
import 'package:faschingsplaner/views/auth/root_page.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'views/add_carnival_view/add_carnival_view.dart';
import 'views/home_view/home_view.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// Set default `_initialized` and `_error` state to false
bool _initialized = false;
bool _error = false;
// final Future<FirebaseApp> _fbApp = Firebase.initializeApp();
// Define an async function to initialize FlutterFire
void initializeFlutterFire() async {
try {
await Firebase.initializeApp();
setState(() {
_initialized = true;
});
} catch(e) {
setState(() {
_error = true;
});
}
}
#override
void initState() {
initializeFlutterFire();
super.initState();
}
#override
Widget build(BuildContext context) {
if (_error) {
return Center(
child: Text('Error occured: $_error'),
);
}
if (!_initialized) {
return Center(
child: CircularProgressIndicator(),
);
}
return MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: [GlobalMaterialLocalizations.delegate],
supportedLocales: [
const Locale('de'),
],
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Color.fromRGBO(58, 66, 86, 1.0),
accentColor: Colors.blue),
initialRoute: '/authentication',
routes: {
RootPage.routeName: (context) => RootPage(auth: new Auth()),
HomeView.routeName: (context) => HomeView(),
AddView.routeName: (context) => AddView(),
});
}
}
At this point, I have totally no clue what could be wrong. Please consider that I am pretty new to flutter.