How to implement Flutter Localization with Provider? - android

I wanted to make my application to work in 2 different language.
I guess problem is that I cannot handle Cosumer in multiprovider ,could it be?
I am using flutter_localization package alongside with flutter provider package;
Here is my code:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
AppLanguage appLanguage = AppLanguage();
await appLanguage.fetchLocale();
SharedPreferences.getInstance().then((prefs) {
var darkModeOn = prefs.getBool('darkMode') ?? true;
runApp(
ChangeNotifierProvider<ThemeManager>(
builder: (_) => ThemeManager(lightTheme),
child: MyApp(appLanguage: appLanguage),
),
);
});
}
Class My App
class MyApp extends StatelessWidget {
final AppLanguage appLanguage;
MyApp({this.appLanguage});
#override
Widget build(BuildContext context) {
final themeNotifier = Provider.of<ThemeManager>(context);
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: ApiService()),
ChangeNotifierProxyProvider<ApiService, StudentData>(
builder: (ctx, auth, _) => StudentData(auth.token, auth.school),
)
],
child: Consumer<ApiService>(
builder: (ctx, auth, _) => ChangeNotifierProvider<AppLanguage>(
builder: (_) => appLanguage,
child: Consumer<AppLanguage>(
builder: (context, model, child) => MaterialApp(
locale: appLanguage.appLocal,
supportedLocales: [Locale('ru', 'RU'), Locale('uz', 'UZ')],
localizationsDelegates: [
AppLocalization.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
debugShowCheckedModeBanner: false,
title: "Socratus |Mobile",
theme: themeNotifier.getTheme(),
home: auth.isAuth
? MainScreen()
: FutureBuilder(
future: auth.tryAutoLogin(),
builder: (ctx, authResultSnapshot) => LoginScreen()),
routes: {
MainScreen.routeName: (ctx) => MainScreen(),
ProfilePage.routeName: (ctx) => ProfilePage(),
SettingsPage.routeName: (ctx) => SettingsPage(
appLanguage: appLanguage,
),
ChangePassword.routeName: (ctx) => ChangePassword(),
HomeworkScreen.routeName: (ctx) => HomeworkScreen(),
HWDetails.routeName: (ctx) => HWDetails(),
NewsPage.routeName: (ctx) => NewsPage(),
QuestionAndAnswers.routeName: (ctx) => QuestionAndAnswers(),
MyDownloads.routeName: (ctx) => MyDownloads(),
},
),
),
),
),
);
}
}
Here how I tried to implement
class AppLocalization {
final Locale locale;
AppLocalization(this.locale);
static AppLocalization of(BuildContext context) {
return Localizations.of<AppLocalization>(context, AppLocalization);
}
static const LocalizationsDelegate<AppLocalization> delegate =
_AppLocalizationDelegate();
Map<String, String> _localizedStrings;
Future<bool> load() async {
String jsonString = await rootBundle
.loadString('assets/translations/${locale.languageCode}.json');
Map<String, dynamic> jsonMap = json.decode(jsonString);
_localizedStrings = jsonMap.map((key, value) {
return MapEntry(key, value.toString());
});
return true;
}
String translate(String key) {
return _localizedStrings[key];
}
}
class _AppLocalizationDelegate extends LocalizationsDelegate<AppLocalization> {
// This delegate instance will never change (it doesn't even have fields!)
// It can provide a constant constructor.
const _AppLocalizationDelegate();
#override
bool isSupported(Locale locale) {
// Include all of your supported language codes here
return ['ru', 'uz'].contains(locale.languageCode);
}
#override
Future<AppLocalization> load(Locale locale) async {
AppLocalization localizations = new AppLocalization(locale);
await localizations.load();
return localizations;
}
#override
bool shouldReload(_AppLocalizationDelegate old) => false;
}
And my provider :
class AppLanguage extends ChangeNotifier {
Locale _appLocale = Locale('ru');
Locale get appLocal => _appLocale ?? Locale("ru");
fetchLocale() async {
var prefs = await SharedPreferences.getInstance();
if (prefs.getString('language_code') == null) {
_appLocale = Locale('ru');
return Null;
}
_appLocale = Locale(prefs.getString('language_code'));
return Null;
}
void changeLanguage(Locale type) async {
var prefs = await SharedPreferences.getInstance();
if (_appLocale == type) {
return;
}
if (type == Locale("uz")) {
_appLocale = Locale("uz");
await prefs.setString('language_code', 'uz');
await prefs.setString('countryCode', 'UZ');
} else {
_appLocale = Locale("ru");
await prefs.setString('language_code', 'ru');
await prefs.setString('countryCode', 'RU');
}
notifyListeners();
}
}
If this is not a correct way , how can I implement this feature ?

The way you are trying to implement is explained in this article, if you'd like a different approach I'd suggest you to check out Easy Localization from pub.dev which is currently handling well all the Flutter internationalization features.

MY SOLUTION
Note:
Used get_storage for persistent key/value storage.
The above question has been asked in reference to this tutorial
main.dart
void main() async {
...
///Initialize Storage
await GetStorage.init();
...
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
...
///Check for locale
AppLanguage language = AppLanguage();
language.fetchLocale();
final providers = [
/**
* Add all providers in here like below
* ChangeNotifierProvider<ThemeModel>(create: (context) => ThemeModel(),),
*/
...
///Language provider
ChangeNotifierProvider<AppLanguage>(
create: (context) => language,
),
...
];
return MultiProvider(
providers: providers,
child: Consumer<AppLanguage>(
builder: (context, model, _) {
/**
* Access other providers apart from language like below
* Provider.of<ThemeModel>(context).theme,
*/
...
return MaterialApp(
locale: model.appLocal,
supportedLocales: [
Locale('ru', 'RU'),
Locale('uz', 'UZ'),
],
localizationsDelegates: [
AppLocalization.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
...
);
},
),
);
}
}
Here is the provider language.dart
class AppLanguage extends ChangeNotifier {
Locale _appLocale = Locale('ru');
Locale get appLocal => _appLocale;
fetchLocale() async {
var storage = GetStorage();
if (!(storage.hasData('language_code'))) {
_appLocale = Locale('ru');
return Null;
}
_appLocale = Locale(storage.read('language_code'));
return Null;
}
Future<void> updateLanguage(Locale type) async {
var storage = GetStorage();
if (_appLocale == type) {
return;
}
if (type == Locale('uz')) {
_appLocale = Locale('sw');
await storage.write('language_code', 'uz');
await storage.write('countryCode', 'UZ');
} else {
_appLocale = Locale('ru');
await storage.write('language_code', 'ru');
await storage.write('countryCode', 'RU');
}
notifyListeners();
}
}
The AppLocalization class on the above question has been implemented correctly.
USAGE
Widget build(BuildContext context) {
...
final languageModel = Provider.of<LanguageModel>(context);
...
return Scaffold(
body: ListView(
children: [
GestureDetector(
...
onTap: () {
languageModel.appLocal == Locale('ru')
? languageModel.updateLanguage(Locale('uz'))
: languageModel.updateLanguage(Locale('ru'));
},
),
],
),
);
}
Thoughts
Language provider should be consumed first and the rest to follow i.e ApiService.. e.t.c.
Any misconception on my code and bugs, don't hesitate to comment. I will reply as soon as possible.

Related

ThemeMode is null in debug console

I am implementing ThemeMode feature on my fluter app for example if user have saved ThemeMode (light, dark or system) saved in SharedPreferences then it should load on app startup otherwise bydefault it is light. so i am using Provider for state management.
Here is my approach:
on themes_provider.dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/themes.dart';
class ThemesProvider extends ChangeNotifier {
ThemeMode? _themeMode;
ThemeMode? get themeMode => _themeMode;
ThemeData get lightTheme => ThemeManager().lightTheme;
ThemeData get darkTheme => ThemeManager().darkTheme;
void initThemeMode() async {
final _prefs = await SharedPreferences.getInstance();
final _currentTheme = _prefs.getString('theme') ?? 'light';
if (_currentTheme == 'light') {
_themeMode = ThemeMode.light;
} else if (_currentTheme == 'dark') {
_themeMode = ThemeMode.dark;
} else {
_themeMode = ThemeMode.system;
}
notifyListeners();
}
void setThemeMode(int value) async {
final _prefs = await SharedPreferences.getInstance();
_prefs.setInt('theme', value);
notifyListeners();
}
}
and then on main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserProvider()),
ChangeNotifierProvider(
create: (context) => ThemesProvider(),
)
],
builder: (context, snapshot) {
final _themeNotifier =
Provider.of<ThemesProvider>(context, listen: false);
_themeNotifier.initThemeMode();
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: _themeNotifier.lightTheme,
darkTheme: _themeNotifier.darkTheme,
themeMode: _themeNotifier.themeMode,
home: HomeScreen(),
onGenerateRoute: RouteGenerator.generateRoute,
initialRoute: RouteGenerator.loginRoute,
);
});
}
}
But in debug console i am getting null value on _themeNotifier.themeMode
please help.

Flutter asset database error: The getter 'length' was called on null. Receiver: null Tried calling: length

I added my existing database.db file to my project with sqflite. No errors encountered, everything works fine, but... Flutter debug console says:
Restarted application in 772ms.
════════ Exception caught by widgets library ═══════════════════════════════════
The following NoSuchMethodError was thrown building FutureBuilder<List<Countries>>(dirty, state: _FutureBuilderState<List<Countries>>#d0317):
The getter 'length' was called on null.
Receiver: null
Tried calling: length
The relevant error-causing widget was
FutureBuilder<List<Countries>>
When the exception was thrown, this was the stack
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1 _HomeScreen.buildBody.<anonymous closure>
#2 _FutureBuilderState.build
#3 StatefulElement.build
#4 ComponentElement.performRebuild
...
════════════════════════════════════════════════════════════════════════════════
I/flutter (14052): Opening existing database
Here is my model Country.dart :
class Countries {
int countryId;
String countryName;
String countryImageURL;
//Constructor
Countries({this.countryId, this.countryName, this.countryImageURL});
// Extract a Product Object from a Map Oject
Countries.fromMap(Map<String, dynamic> map) {
countryId = map['country_id'];
countryName = map['country_name'];
countryImageURL = map['image'];
}
Map<String, dynamic> toMap() {
var map = <String, dynamic>{
'country_name': countryName,
'image': countryImageURL
};
return map;
}
}
Here is my database_helper.dart file:
import 'dart:async';
import 'dart:io';
import 'package:city_travel_guide/model/Country.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'dart:typed_data';
import 'package:flutter/services.dart';
class DbHelper {
static Database _db;
Future<Database> get db async {
if (_db != null) {
return _db;
} else {
_db = await initDb();
return _db;
}
}
initDb() async {
var dbFolder = await getDatabasesPath();
String path = join(dbFolder, 'app.db');
var exists = await databaseExists(path);
if (!exists) {
// Should happen only the first time you launch your application
print("Creating new copy from asset");
// Make sure the parent directory exists
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
// Copy from asset
ByteData data = await rootBundle.load(join("assets", "example.db"));
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
// Write and flush the bytes written
await File(path).writeAsBytes(bytes, flush: true);
} else {
print("Opening existing database");
}
// open the database
return await openDatabase(path);
}
Future<List<Countries>> getCountries() async {
var dbClient = await db;
var result = await dbClient.query('Country', orderBy: 'countryId');
return result.map((data) => Countries.fromMap(data)).toList();
}
Here is my main.dart file:
import 'package:city_travel_guide/data/database_helper.dart';
import 'package:city_travel_guide/model/Country.dart';
import 'package:flutter/material.dart';
import 'widgets/maindrawer.dart';
import 'pages/search.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'City Travel Guide',
theme: ThemeData.dark(),
debugShowCheckedModeBanner: false,
home: MyHome());
}
}
class MyHome extends StatefulWidget {
#override
_HomeScreen createState() => _HomeScreen();
}
class _HomeScreen extends State<MyHome> {
List<Countries> countries;
final dbHelper = DbHelper();
#override
void initState() {
dbHelper.initDb();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'City Travel Guide',
style: Theme.of(context).primaryTextTheme.headline6,
),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.search),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SearchScreen()),
);
}),
IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
],
),
drawer: Drawer(child: MainDrawer()),
body: buildBody(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {},
));
}
buildBody() {
return FutureBuilder<List<Countries>>(
future: dbHelper.getCountries(),
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(title: Text(snapshot.data[index].countryName));
},
);
});
}
}
How can I list items on my asset database and view it in application?
FutureBuilder is an asynchronous request. Always check that snapshot has data before building your list.
do:
buildBody() {
return FutureBuilder<List<Countries>>(
future: dbHelper.getCountries(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data.length > 0) // This ensures that you have at least one or more countries available.
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(title: Text(snapshot.data[index].countryName));
},
);
else if (snapshot.hasData && snapshot.data.length == 0)
return Center(child:Text("There are no countries available"));
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).primaryColor),
)); // This would display a loading animation before your data is ready
});
}
Its to easy you must check if there is a data comming from the future or not before using the snapshot.data.lenght because if the snapshot.data is null(the opperation not finished yet) then lenght was calling on null so you must do it
The correct code
buildBody() {
return FutureBuilder<List<Countries>>(
future: dbHelper.getCountries(),
builder: (context, snapshot) {
if(snapshot.hasdata&&snapshot.data.runtimetype==List){
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(title: Text(snapshot.data[index].countryName));
},
);
}else{
return Proggresindicator()//or any loading widgets
}
});
}
}
and you can add check for any execption happens during the future

Button to change the language Flutter

I am building an app in Flutter, so far I am using the Internationalization with JSON where the language of the app is based on the language that the user has as default in his phone its working pretty well, but I would like to give the user a chance to change the language without changing phone the system language settings, by only clicking in a button and then the application change the language without going through the settings.
Here is the code:
The Main:
import 'package:flutter/material.dart';
import 'package:flutter_app_darkmode/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) => MaterialApp(
supportedLocales: [
Locale('en', "ZA"),
Locale('pt', "MZ"),
],
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
localeResolutionCallback: (locale, supportedLocales) {
for (var supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale.languageCode &&
supportedLocale.countryCode == locale.countryCode) {
return supportedLocale;
} else {
if (MyHomePage.local != null) {
for (int i = 0; i < supportedLocales.length; i++) {
if (MyHomePage.local == supportedLocales.elementAt(i)) {
return supportedLocales.elementAt(i);
}}}}}
return supportedLocales.first;
},
home: MyHomePage(),
);}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
class _MyHomePageState extends State<MyHomePage> {
getLocale() {
Locale myLocale = Localizations.localeOf(context);
print(myLocale);}
#override
Widget build(BuildContext context) {
getLocale();
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
AppLocalizations.of(context).translate('first_string'),
style: TextStyle(fontSize: 25),
textAlign: TextAlign.center,),
Text(
AppLocalizations.of(context).translate('second_string'),
style: TextStyle(fontSize: 25),
textAlign: TextAlign.center,),
RaisedButton(
child: Text('PT'),
onPressed: () {},
),],),),),);}}
The app_locations class:
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class AppLocalizations {
final Locale locale;
AppLocalizations(this.locale);
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
Map<String, String> _localizedStrings;
Future<bool> load() async {
String jsonString =
await rootBundle.loadString('lang/${locale.languageCode}.json');
Map<String, dynamic> jsonMap = json.decode(jsonString);
_localizedStrings = jsonMap.map((key, value) {
return MapEntry(key, value.toString());
});
return true;}
String translate(String key) {
return _localizedStrings[key];}}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
#override
bool isSupported(Locale locale) {
return ['en', 'pt'].contains(locale.languageCode);}
#override
Future<AppLocalizations> load(Locale locale) async {
AppLocalizations localizations = new AppLocalizations(locale);
await localizations.load();
return localizations;}
#override
bool shouldReload(_AppLocalizationsDelegate old) => false;}
You can set the locale property of MaterialApp with your favourite state management method. For example:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
static _MyAppState of(BuildContext context) => context.findAncestorStateOfType<_MyAppState>();
}
class _MyAppState extends State<MyApp> {
Locale _locale;
void setLocale(Locale value) {
setState(() {
_locale = value;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
locale: _locale,
home: Dashboard(),
);
}
}
class Dashboard extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
TextButton(
child: Text("Set locale to German"),
onPressed: () => MyApp.of(context).setLocale(Locale.fromSubtags(languageCode: 'de')),
),
TextButton(
child: Text("Set locale to English"),
onPressed: () => MyApp.of(context).setLocale(Locale.fromSubtags(languageCode: 'en')),
),
],
);
}
}
You have to use Localizations given from Flutter. You have to use custom delegate and JSON files for your supported languages.
I implemented using bloc
Steps to follow,
Create a folder assets/languages/ in the root folder
Create JSON files for your supported languages.
Like: en.json, es.json
Create a key, value pairs for your strings in each file accordingly with their specific language strings
In main.dart create default locale, supportedLocales and localizationsDelegates.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:movie_app/common/constants/languages.dart';
import 'package:movie_app/presentation/app_localizations.dart';
import 'package:movie_app/presentation/blocs/language/language_bloc.dart';
import 'package:movie_app/presentation/journeys/home/home_screen.dart';
class MovieApp extends StatefulWidget {
#override
_MovieAppState createState() => _MovieAppState();
}
class _MovieAppState extends State<MovieApp> {
LanguageBloc _languageBloc;
#override
void initState() {
_languageBloc = LanguageBloc();
super.initState();
}
#override
void dispose() {
_languageBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocProvider<LanguageBloc>.value(
value: _languageBloc,
child: BlocBuilder<LanguageBloc, LanguageState>(
builder: (context, state) {
if (state is LanguageLoaded) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Movie App',
home: HomeScreen(),
supportedLocales:
Languages.languages.map((e) => Locale(e.code)).toList(),
locale: state.locale,
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
);
}
return SizedBox.shrink();
},
),
);
}
}
Now create Languages Language models and constants
class LanguageEntity {
final String code;
final String value;
const LanguageEntity({
this.code,
this.value,
});
}
class Languages {
const Languages._();
static const languages = [
LanguageEntity(code: 'en', value: 'English'),
LanguageEntity(code: 'es', value: 'Spanish'),
];
}
Now Create app localization delegate
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:movie_app/common/constants/languages.dart';
class AppLocalizations {
final Locale locale;
AppLocalizations(this.locale);
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationDelagate();
static AppLocalizations of(context) =>
Localizations.of<AppLocalizations>(context, AppLocalizations);
Map<String, String> _localisedString;
Future<bool> load() async {
final jsonString = await rootBundle
.loadString('assets/languages/${locale.languageCode}.json');
final Map<String, dynamic> jsonMap = json.decode(jsonString);
_localisedString =
jsonMap.map((key, value) => MapEntry(key, value.toString()));
return true;
}
String translate(String key) {
return _localisedString[key];
}
}
class _AppLocalizationDelagate extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationDelagate();
#override
bool isSupported(Locale locale) {
return Languages.languages
.map((e) => e.code)
.toList()
.contains(locale.languageCode);
}
#override
bool shouldReload(covariant LocalizationsDelegate old) {
return false;
}
#override
Future<AppLocalizations> load(Locale locale) async {
AppLocalizations appLocalizations = AppLocalizations(locale);
await appLocalizations.load();
return appLocalizations;
}
}
Now create blocs
import 'dart:async';
import 'package:bloc/bloc.dart';
// import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:movie_app/common/constants/languages.dart';
import 'package:movie_app/domain/entities/language_entity.dart';
part 'language_event.dart';
part 'language_state.dart';
class LanguageBloc extends Bloc<LanguageEvent, LanguageState> {
LanguageBloc() : super(LanguageLoaded(Locale(Languages.languages[0].code)));
#override
Stream<LanguageState> mapEventToState(
LanguageEvent event,
) async* {
if (event is ToggleLanguageEvent) {
yield LanguageLoaded(Locale(event.language.code));
}
}
}
8.Now create event
part of 'language_bloc.dart';
abstract class LanguageEvent {
const LanguageEvent();
}
class ToggleLanguageEvent extends LanguageEvent {
final LanguageEntity language;
ToggleLanguageEvent(this.language);
}
Now create state
part of 'language_bloc.dart';
abstract class LanguageState {
const LanguageState();
}
class LanguageLoaded extends LanguageState {
final Locale locale;
LanguageLoaded(this.locale);
}
10.Now Create button to change languages.
RaisedButton(child: ,RaisedButton(child: Text('Switch',
onPressed: (int index) {
BlocProvider.of<LanguageBloc>(context).add(
ToggleLanguageEvent(
Languages.languages[index], // index value can be 0 or 1 in our case
), // 0 - en, 1 - es
);
Navigator.of(context).pop();
},
);
Also, please refer the link for clear implementation
https://www.youtube.com/watch?v=W-2p3zB1z8k
If you are using provider as state management then you can follow this article .
https://medium.com/flutter-community/flutter-internationalization-the-easy-way-using-provider-and-json-c47caa4212b2
Because accepted answer has some shortcomings I will set new one:
Change locale of App can be done in several ways, here i will show two way:
Way 1 (using rxdart and SharedPreferences plugins):
For more understanding i was created project to explain how do that, you can find it in https://github.com/AnasSafi/flutter_localization_example
Output of project:
Way 2:
Note 1: I was used SharedPreferences plugin to save locale which
client select.
Note 2: Replace ar and ps with your default locale ...
Full code of main.dart file:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized(); //To solve problem (ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized)
runApp(MainApp());
}
class MainApp extends StatefulWidget {
const MainApp({Key? key}) : super(key: key);
/*
To Change Locale of App
*/
static void setLocale(BuildContext context, Locale newLocale) async {
_MainAppState? state = context.findAncestorStateOfType<_MainAppState>();
var prefs = await SharedPreferences.getInstance();
prefs.setString('languageCode', newLocale.languageCode);
prefs.setString('countryCode', "");
state?.setState(() {
state._locale = newLocale;
});
}
#override
_MainAppState createState() => _MainAppState();
}
class _MainAppState extends State<MainApp> {
Locale _locale = Locale('ar', 'ps');
#override
void initState() {
super.initState();
this._fetchLocale().then((locale) {
setState(() {
this._locale = locale;
});
});
}
/*
To get local from SharedPreferences if exists
*/
Future<Locale> _fetchLocale() async {
var prefs = await SharedPreferences.getInstance();
String languageCode = prefs.getString('languageCode') ?? 'ar';
String countryCode = prefs.getString('countryCode') ?? 'ps';
return Locale(languageCode, countryCode);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
locale: _locale,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
const FallbackCupertinoLocalisationsDelegate(),
],
supportedLocales: [
const Locale('en', ''), // English, no country code
const Locale('ar', ''), // Arabic, no country code
],
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: InitializeApp(), // here use your own home name...
);
}
}
/*
To solve problem of hold press on inputs
*/
class FallbackCupertinoLocalisationsDelegate extends LocalizationsDelegate<CupertinoLocalizations> {
const FallbackCupertinoLocalisationsDelegate();
#override
bool isSupported(Locale locale) => true;
#override
Future<CupertinoLocalizations> load(Locale locale) =>
DefaultCupertinoLocalizations.load(locale);
#override
bool shouldReload(FallbackCupertinoLocalisationsDelegate old) => false;
}
Now from any other class you need to change locale from it, you can use my way:
Import main.dart file, Then use my simple DropdownButton:
Note: replace value:
AppLocalizations.of(context)!.locale.toString(), this line with your
way to get current locale of app...
import 'package:yout_project_name/main.dart';
DropdownButton(
onChanged: (v) => setState(() {
MainApp.setLocale(context, Locale(v.toString(), ""));
}),
value: AppLocalizations.of(context)!.locale.toString(), // change this line with your way to get current locale to select it as default in dropdown
items: [
DropdownMenuItem(
child: Text( 'English'), value: 'en'
),
DropdownMenuItem(
child: Text( 'العربية'), value: 'ar'
),
],
)
Take a look at the language_builder package in pub.dev
It is very easy to use. By wrapping your root widget with LanguageBuilder you can configure your app's language.
Tell your app to use the phones' language or change it manually from the app.
https://pub.dev/packages/language_builder

How to reload screen on future data

I have TabBarView with six tabs.
I'm trying to show all the installed apps in first tab.
Even after apps is filled CircularProgressIndicator is displayed. Apps are listed once the first tab is revisited.
AppScreenC is called for first tab.
final model = AppModel();
class AppScreenC extends StatefulWidget {
#override
_AppScreenCState createState() => _AppScreenCState();
}
class _AppScreenCState extends State<AppScreenC> {
List<Application> apps = model.loadedApps();
#override
Widget build(BuildContext context) => _buildApps();
Widget _buildApps() => apps != null
? ListView.builder(
itemCount: apps.length,
itemBuilder: (BuildContext context, int index) =>
_buildRow(apps[index]))
: Center(child: CircularProgressIndicator());
Widget _buildRow(ApplicationWithIcon app) {
final saved = model.getApps().contains(app.apkFilePath);
return ListTile(
leading: Image.memory(app.icon, height: 40),
trailing: saved
? Icon(Icons.check_circle, color: Colors.deepPurple[400])
: Icon(Icons.check_circle_outline),
title: Text(app.appName),
onTap: () => setState(() => saved
? model.removeApp(app.apkFilePath)
: model.addApp(app.apkFilePath)),
);
}
}
AppModel class has all the necessary methods.
class AppModel{
final _saved = Set<String>();
List<Application> apps;
AppModel() {
loadApps();
}
Set<String> getApps() {
return _saved;
}
addApp(String apkPath) {
_saved.add(apkPath);
}
removeApp(String apkPath) {
_saved.remove(apkPath);
}
loadApps() async {
apps = await DeviceApps.getInstalledApplications(
onlyAppsWithLaunchIntent: true,
includeSystemApps: true,
includeAppIcons: true);
apps.sort((a, b) => a.appName.compareTo(b.appName));
}
loadedApps() => apps;
}
This is happening because apps is null, when the screen was called first time. It loads the apps in background. Upon visiting the screen again, apps are displayed.
Any help is welcome
What you can do is calling setState() after your Function is done. You need to change loadedApp to return a Future:
class AppScreenC extends StatefulWidget {
#override
_AppScreenCState createState() => _AppScreenCState();
}
class _AppScreenCState extends State<AppScreenC> {
#override
void initState(){
super.initState();
model.loadApps().then((loadedApps){ //loadApps() will return apps and you don't need loadedApps() method anymore
setState((){ //rebuilds the screen
apps = loadedApps
})});
}
#override
Widget build(BuildContext context) => _buildApps();
Widget _buildApps() => apps != null
? ListView.builder(
itemCount: apps.length,
itemBuilder: (BuildContext context, int index) =>
_buildRow(apps[index]))
: Center(child: CircularProgressIndicator());
Widget _buildRow(ApplicationWithIcon app) {
final saved = model.getApps().contains(app.apkFilePath);
return ListTile(
leading: Image.memory(app.icon, height: 40),
trailing: saved
? Icon(Icons.check_circle, color: Colors.deepPurple[400])
: Icon(Icons.check_circle_outline),
title: Text(app.appName),
onTap: () => setState(() => saved
? model.removeApp(app.apkFilePath)
: model.addApp(app.apkFilePath)),
);
}
}
And your AppModel will look like this:
class AppModel{
final _saved = Set<String>();
List<Application> apps;
AppModel() {
loadApps();
}
Set<String> getApps() {
return _saved;
}
addApp(String apkPath) {
_saved.add(apkPath);
}
removeApp(String apkPath) {
_saved.remove(apkPath);
}
Future loadApps() async {
apps = await DeviceApps.getInstalledApplications(
onlyAppsWithLaunchIntent: true,
includeSystemApps: true,
includeAppIcons: true);
apps.sort((a, b) => a.appName.compareTo(b.appName));
return Future.value(apps);
}
}
You can also use FutureBuilder as suggested in the comments

Flutter multiprovider and consumer getting null in child widget

Im having issues trying to get the current user from a child widget. When I login I can see the response if I print from AuthProvider. However, when I try to get the currentUser from a child widget the value is null.
Login in.
Calling query from UsersProvider.
setting the currentUser variable in said provider.
go back to AuthProvider and finish login in the user.
main
#override
Widget build(BuildContext context) {
print('build Main');
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: AuthProvider(),
),
ChangeNotifierProvider.value(
value: UsersProvider(),
),
],
child: Consumer<AuthProvider>(
builder: (ctx, auth, _) => MaterialApp(
title: 'Wayuu',
theme: ThemeData(
primarySwatch: Colors.indigo,
accentColor: Colors.purpleAccent,
// textTheme: ThemeData.dark().textTheme.copyWith(
// title: TextStyle(
// color: Theme.of(context).accentColor,
// fontWeight: FontWeight.bold,
// fontSize: 20,
// ),
// ),
),
home: auth.isAuthenticated
? HomeState()
: FutureBuilder(
future: auth.isAuth(),
builder: (ctx, authResultSnapshot) =>
authResultSnapshot.connectionState ==
ConnectionState.waiting
? Splash()
: Login()),
routes: {
Home.routeName: (context) => Home(),
Profile.routeName: (context) => Profile(),
ForgotPassword.routeName: (context) => ForgotPassword(),
RegisterScreen.routeName: (context) => RegisterScreen(),
Favorites.routeName: (context) => Favorites(),
Podcast.routeName: (context) => Podcast(),
MyAccount.routeName: (context) => MyAccount(),
},
),
),
);
}
}
AuthProvider
bool _userAuthenticated = false;
bool get isAuthenticated {
return _userAuthenticated;
}
Future<bool> isAuth() async {
final FirebaseUser response = await FirebaseAuth.instance.currentUser();
if (response != null) {
_userAuthenticated = true;
notifyListeners();
}
return _userAuthenticated;
}
Future<void> loginUser(Map<String, String> loginData) async {
UsersProvider usersProvider = UsersProvider();
try {
final response = await FirebaseAuth.instance.signInWithEmailAndPassword(
email: loginData['usernameOrEmail'], password: loginData['password']);
// _currentUserId = response.user.uid;
await usersProvider.getUserById(response.user.uid);
await storeUserIdInSharePreferences(response.user.uid);
_userAuthenticated = true;
notifyListeners();
} catch (error) {
throw HttpException(error.toString());
}
}
}
UsersProvider
class UsersProvider with ChangeNotifier {
Map<String, dynamic> _currentUser = {};
Map<String, dynamic> get currentUser {
return {..._currentUser};
}
Future<void> getUserById(String userId) async {
try {
QuerySnapshot querySnapshot = await Firestore.instance
.collection('users')
.where('id', isEqualTo: userId)
.getDocuments();
querySnapshot.documents.forEach((documentData) {
_currentUser = {
'id': documentData.data['id'],
'fullName': documentData.data['fullName'],
'email': documentData.data['email'],
'username': documentData.data['username'],
'profilePicture': documentData.data['profilePicture'],
'bio': documentData.data['bio'],
'instagram': documentData.data['instagram'],
'facebook': documentData.data['facebook'],
'web': documentData.data['web']
};
});
notifyListeners();
} catch (error) {
print('error from query');
print(error);
throw HttpException(error.toString());
}
}
}
Profile Widget
Widget build(BuildContext context) {
print('build profile widget');
final currentUser = Provider.of<UsersProvider>(context).currentUser;
print(currentUser);
I get null from here when my "Profile" widget executes
final currentUser = Provider.of<UsersProvider>(context).currentUser;
Thank you in advance !
The instance of UsersProvider that you create inside of loginUser exists only in the scope of that method. Refactor the method to return the response and then use it to update the provider.
var response = await authProvider.loginUser(loginData);
await Provider.of<UsersProvider>(context).getUserById(response.user.uid);
Then when you call the current user it'll be the same instance that will be updated.

Categories

Resources