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
Related
I am developing a mobile application with Flutter. And I have a code like this:
PaginationController.dart:
import 'package:get/get.dart';
import 'package:keycehennemi/functions/SecureStorage.dart';
class PaginationController extends GetxController {
RxString ProductImage = RxString("");
}
ListView.dart:
onTap: () {
setState(() {
paginationController.ProductImage.value = snapshot.data.docs[index].data()["Image"];
});
Get.to(const ProductDetailsPage());
},
The page I'm trying to show the image:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:keycehennemi/controllers/PaginationController.dart';
class ProductDetailsPage extends StatefulWidget {
const ProductDetailsPage({Key key}) : super(key: key);
#override
State<ProductDetailsPage> createState() => _ProductDetailsPageState();
}
PaginationController paginationController = PaginationController();
String ImageURL = "";
class _ProductDetailsPageState extends State<ProductDetailsPage> {
#override
void initState() {
super.initState();
setState(() {
ImageURL = paginationController.ProductImage.value;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
Image.network(ImageURL, fit: BoxFit.cover),
],
),
),
);
}
}
When I run these codes, I get an error like this:
How can I solve this problem? I haven't been able to figure it out for days, it bothers me.
When I print the ProductImage in the PaginatonController I see the value is given. So the value is assigned to ProductImage. But I can't display the image on the page I'm trying to display and I'm getting an error.
I know this may not be conventional but this is how i use GetX (It can help you):
First I make a GlobalBindings.dart file:
class GlobalBindings implements Bindings {
#override
void dependencies() {
Get.lazyPut<PaginationController>(() => PaginationController(),
fenix: true);
}
Then I do this in my void main:
void main async {
GlobalBindings().dependencies();
runApp(MyApp());
}
Then in my PaginationController.dart:
class PaginationController extends GetxController {
String _productImage = "";
String get productImage => _productImage;
setProductImage(String value){
_productImage = value;
update();
}
}
Then inside productImage:
class ProductDetailsPage extends StatelessWidget {
ProductDetailsPage({Key? key})
: super(key: key);
#override
Widget build(BuildContext context) {
//Call GetBuilder anywhere in the app
return GetBuilder<PaginationController>(builder:(controller)=> Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
if(controller.productImage.isNotEmpty)
Image.network(controller.productImage, fit: BoxFit.cover),
TextButton(child:Text("Button"),onPressed:(){
controller.setProductImage("You can set image url here");
})
],
),
),
));
}
}
Note: You can use the GetBuilder any where.
I am trying to do App Localization using Cubit. There was only one online tutorial that was using BloC but not using Cubit. I wrote my own code but it is not translating. Maybe there is a small mistake. Below is my code.
/* Localization.dart file */
import 'dart:async';
import 'dart:convert';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Localization {
Localization(this.locale);
final Locale locale;
static Localization? of(BuildContext context) {
return Localizations.of<Localization>(context, Localization);
}
late Map<String, String> _sentences;
Future<bool> load() async {
String data = await rootBundle.loadString('resources/lang/${locale.languageCode}.json');
Map<String, dynamic> _result = json.decode(data);
_sentences = {};
_result.forEach((String key, dynamic value) {
_sentences[key] = value.toString();
});
return true;
}
String? getString(String key) {
return _sentences[key];
}
static const LocalizationsDelegate <Localization> delegate = _WebLocalizationDelegate();
}
class _WebLocalizationDelegate extends LocalizationsDelegate<Localization> {
const _WebLocalizationDelegate();
#override
bool isSupported(Locale locale) => ['sv', 'en'].contains(locale.languageCode);
#override
Future<Localization> load(Locale locale) async {
Localization localizations = Localization(locale);
await localizations.load();
return localizations;
}
#override
bool shouldReload(LocalizationsDelegate<Localization> old) => false;
}
/* Localization Cubit class */
import 'package:equatable/equatable.dart';
import 'package:bloc/bloc.dart';
import 'dart:ui';
import 'package:flutter/material.dart';
part 'localization_state.dart';
class LocalizationCubit extends Cubit<LocalizationStates> {
LocalizationCubit(LocalizationStates initialState) : super(initialState);
LocalizationStates get initialState => LocalizationStates.initial();
void changeLanguage(String lang, String country) {
emit(state.copyWith(locale: Locale(lang, country)));
}
}
/* Localization State class*/
part of 'localization_cubit.dart';
class LocalizationStates extends Equatable{
final Locale locale;
LocalizationStates({required this.locale});
factory LocalizationStates.initial()=>LocalizationStates(locale: Locale('sv', 'SE'));
LocalizationStates copyWith({required Locale locale}) => LocalizationStates(locale: locale?? this.locale);
#override
List<Object?> get props => [locale];
}
/* In main file, this is how delegates are initialized and Bloc Provider is used in MultiBlocProvider*/
localizationsDelegates: const [
Localization.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en', 'US'),
Locale('sv', 'SE'),
],
BlocProvider<LocalizationCubit>(
lazy: false,
create: (_) => LocalizationCubit(
LocalizationStates.initial(),
),
),
/* I am using Drop down menu in my App Bar to select language, You can see that I am calling changeLanguage function, that is emitting a required state */
List languages = ["Swedish", "English"]; Declared above
String lang = "Swedish";
DropdownButton(
value: lang,
onChanged: (value){
setState(() {
if (value == 'Swedish') {
BlocProvider.of<LocalizationCubit>(context).changeLanguage('sv', 'SE');
} else
{
BlocProvider.of<LocalizationCubit>(context).changeLanguage('en', 'US');
}
lang = value.toString();
});
},
items: languages.map((value) {
return DropdownMenuItem(
value: value,
child: Text(
value,
style: TextStyle(color: Colors.black),
),
);
}).toList(),
),
Now in the file, where I need to translate, I am doing this. Here, I am not getting translated text. There must be something missing.
#override
Widget build(BuildContext context) {
return BlocBuilder<LocalizationCubit, LocalizationStates>(
builder: (context, state) {
return Scaffold(
body: Center(Text(Localization.of(context)!.getString('username').toString(),));
);}}
Code link: https://github.com/wasimsafdar/Internationalization
i think the on Pressed function in elevated button is null but i dont understand why
my main file where i am using List and Map to create and switch questions and answers
answers are on the buttons and they are printed on them but they are greyed out
import './quiz.dart';
import './result.dart';
void main() => runApp(TestApp());
#override
class TestApp extends StatefulWidget {
const TestApp({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _TestAppState();
}
}
class _TestAppState extends State<TestApp> {
var _i = 0;
final _question = const [
{
'q1': 'whats the capital of India',
'a1': ['Delhi', 'Mumbai', 'Chennai', 'Bangalore'],
},
{
'q1': 'whats the Language of India',
'a1': ['Sanskrit', 'Bengali', 'Hindi', 'Kannada'],
},
{
'q1': 'whats the continent India is located in',
'a1': ['Africa', 'Asia', 'America', 'Australia'],
},
{
'q1': 'whats second most spoken language in India',
'a1': ['Hindi', 'Gujarati', 'Marathi', 'English'],
},
];
_answeredQ() {
setState(() {
_i = _i + 1;
});
// return 0;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text("Test App!"),
),
body: _i < _question.length
? Quiz(qMap: _question, aFunction: _answeredQ(), index: _i)
: Result(),
),
);
}
}
**here's my Quiz class using as a custom widget
import './questionText.dart';
import './answer.dart';
class Quiz extends StatelessWidget {
final List<Map<String, Object>> qMap;
final aFunction;
final int index;
Quiz({required this.qMap, required this.aFunction, required this.index});
#override
Widget build(BuildContext context) {
return Column(
children: [
Question(
qMap[index]['q1'],
),
...(qMap[index]['a1'] as List<String>).map((ans) {
return AnswerW(aFunction, ans);
}).toList()
],
);
}
}
and here's the button custom widget class
class AnswerW extends StatelessWidget {
final selAns;
final String answerText;
AnswerW( this.selAns, this.answerText);
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: EdgeInsets.all(10),
child: ElevatedButton(onPressed: selAns,
child: Text(answerText),
),
);
}
}
In ? Quiz(qMap: _question, aFunction: _answeredQ(), index: _i) You are passing the return value of _answeredQ(), not the actual function itself. You can change this to just _answeredQ (without the "()") or aFunction: () => _answeredQ()
FWIW It's good in dart to take advantage of strong typing. It provides you with better error messages and better linting. Because you don't have any types for most of your variables they can be anything, and the linter has a hard time trying to figure out if you have a type mismatch.
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.
I am completly new to Flutter and Stackoverflow. This is my first question to be in fact so please forgive me if I totaly fail at asking this question. I am trying to make a simple Flutter app that provides a ListView of questions and a checkbox beside each. The user can then choose which question they want to answer. My problem is that when the user checks any of the checkboxes then all get checked and vise versa. The questions themselves are retrieved from a backendless database. The code below is what i have so far. I would really appreciate any help anyone can provide me.
import 'package:flutter/material.dart';
class Questions extends StatefulWidget {
final List<Map> questionList;
Questions(this.questionList);
#override
_QuestionsState createState() => _QuestionsState();
}
class _QuestionsState extends State<Questions> {
bool _questionSelected = true;
Widget _buildQuestionItem(BuildContext context, int index) {
return ListTile(
title: Text(widget.questionList[index]['question']),
trailing: Checkbox(
value: _questionSelected,
onChanged: (bool val){
setState(() {
_questionSelected = val;
});
},
),
);
}
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.all(10),
itemBuilder: _buildQuestionItem,
itemCount: widget.questionList.length,
);
}
}
UPDATED:
Thankful for Mohammed Ashab Uddin suggestions I feel that I am close to getting this thing to work but I am still getting an error
"RangeError (index): Invalid value: Valid value range is empty: 0"
I think I should have posted the main.dart code where I set the value of the questionList perhaps it is an order of code execution that causes this error so please find my code for main.dart below in hopes it would help in figuring out this issue.
import 'package:flutter/material.dart';
import 'package:backendless_sdk/backendless_sdk.dart';
import 'package:flutter/rendering.dart';
import 'questions.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'RT Database Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Questions'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
static const String API_HOST = "https://api.backendless.com";
static const String APP_ID = "<APP_ID>";
static const String ANDROID_APP_KEY = "<ANDROID_APP_KEY>";
static const String IOS_APP_KEY = "<IOS_APP_KEY>";
IDataStore<Map> questionsStore = Backendless.data.of('Questions');
List<Map> questionsList = [];
var _questionSelected = false;
#override
void initState() {
super.initState();
_initBackendless();
_enableRealTime();
getQuestions();
}
void _initBackendless() {
Backendless.setUrl(API_HOST);
Backendless.initApp(APP_ID, ANDROID_APP_KEY, IOS_APP_KEY);
}
void _enableRealTime() {
EventHandler<Map> rtHandlers = questionsStore.rt();
rtHandlers.addCreateListener((question) {
setState(() {
questionsList = List.from(questionsList);
questionsList.add(question);
});
});
rtHandlers.addUpdateListener((question) {
setState(() {
questionsList = List.from(questionsList
.map((m) => m['objectId'] == question['objectId'] ? question : m));
});
});
rtHandlers.addDeleteListener((question) {
setState(() {
questionsList = List.from(questionsList);
questionsList.removeWhere((m) => m['objectId'] == question['objectId']);
});
});
}
void _selectQuestion(bool newValue) {
setState(() {
_questionSelected = newValue;
});
}
void getQuestions() {
DataQueryBuilder queryBuilder = DataQueryBuilder()
..pageSize = 100
..sortBy = ['created'];
questionsStore
.find(queryBuilder)
.then((response) => setState(() => questionsList = response));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("My Life History"),
),
body: FractionallySizedBox(
heightFactor: 0.5,
child: Questions(questionsList),
),
);
}
}
The variable _questionSelected is a global variable. All the checkbox widgets are using this variable as the value. Therefore, when the variable changes on the onChanged() function, all the values are also changed to the value of _questionSelected.
In this case, you need to keep track of all the values of the checkbox widget. So, you should use an array rather than a single variable.
What I usually do is, create a new list that will contain only the selected elements.
Remove an element if it is not selected and add an element if it is selected.
//generate a list of false values with the length of questionList
List<bool> _questionSelected;
initState(){
_questionSelected = List<bool>.filled(questionList.length, false, growable: true);
super.initState();
}
Widget _buildQuestionItem(BuildContext context, int index) {
return ListTile(
title: Text(widget.questionList[index]['question']),
trailing: Checkbox(
value: _questionSelected[index],
onChanged: (bool val){
setState(() {
_questionSelected[index] = val;
});
},
),
);
}