I am developing an app in Flutter. I'm using flutter_localizations package for localization and intl package for internationalization. For this, I'm using Context in Widgets, but the problem is when I want to use internationalization inside bloc or repositories or other layers except for the UI layer.
What is the best practice for doing internationalization inside Other layers except for UI where we don't have access to Context?
I have tried to use a Singleton, but I don't know if this is the right way.
You have to pass appLocalizations. For example in the Cubits you can do something like:
main.dart
runApp(
MaterialApp(
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''),
const Locale('pl', ''),
],
title: 'MySuperApp',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.orange,
accentColor: Colors.deepOrangeAccent,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: '/',
routes: {
UserAuthScreen.routeName: (context) => const UserAuthScreen(),
HomePage.routeName: (context) => HomePage(),
...
},
builder: (context, child) {
final appLocalizations = AppLocalizations.of(context); //IMPORTANT
return MultiBlocProvider(
providers: [
BlocProvider<ConstantsCubit>(
lazy: true,
create: (context) => ConstantsCubit(
constantsRepository: ConstantsRepository(),
),
),
BlocProvider<UserAuthCubit>(
lazy: true,
create: (context) => UserAuthCubit(
localizations: appLocalizations, //THIS IS WHERE THE MAGIC HAPPENS
repository: UserAuthRepository(),
),
),
BlocProvider<DoerInfoCubit>(
lazy: true,
create: (context) => DoerInfoCubit(
doerInfoRepository: DoerInfoRepository(),
userAuthCubit: BlocProvider.of<UserAuthCubit>(context),
)),
...
],
child: child,
);
},
home:
BlocBuilder<UserAuthCubit, UserAuthState>(builder: (context, state) {
if (state is UserAuthLogged) {
return HomePage();
} else {
return const UserAuthScreen();
}
}),
),
);
In your bloc or cubit:
class UserAuthCubit extends Cubit<UserAuthState> {
final UserAuthRepository repository;
final AppLocalizations localizations;
UserAuthCubit({
#required this.repository,
#required this.localizations,
}) : super(const UserAuthInitial()) {
getUserAuthState();
}
Related
The application needs to implement language switching at runtime. Wrote a bloc with event and state and called BlocBuilder in main.dart. But I don't know how to implement the switch. How can I do that?
In total, the application has two languages.
My bloc:
class LanguageBloc extends Bloc<LanguageEvent, LanguageState> {
LanguageBloc() : super(InitialLang()) {
on<ChangeLang>(
(event, emit) {
emit(NewLang());
},
);
}
#immutable
abstract class LanguageEvent {}
class ChangeLang extends LanguageEvent {}
#immutable
abstract class LanguageState {}
class InitialLang extends LanguageState {}
class NewLang extends LanguageState {}
My main.dart
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => BottomNavyBloc(),
),
BlocProvider(
create: (context) => LanguageBloc(),
),
],
child: BlocBuilder<LanguageBloc, LanguageState>(
builder: (context, state) {
return MaterialApp(
title: 'Flutter Demo',
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: const HomeScreen(),
);
},
),
);
}
My lang btn:
ElevatedButton(onPressed: (){}, child: Text('Switch lang'))
What you can do is to send a variable Locale with the language of your choice, and in your MaterialApp in the locale attribute, you attach it.
Without complications you can use Cubit instead of Bloc, because it is not necessary to have events, then you could do the following:
class LanguageCubit extends Cubit<Locale?> { // change state here, you dont use LanguageState
LanguageCubit() : super(null);
void initialLang () { // your initial lang
emit(
Locale("en", ""),
);
}
void newLang(
bool isEnglish, // in your checkbox you are gonna send the boolean value here
) {
emit(
isEnglish ? Locale("en") : Locale("fr"),
);
}
}
Now in your main, as you have it, it would only look like this:
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => BottomNavyBloc(),
),
BlocProvider(
create: (context) => LanguageBloc(),
),
],
child: BlocBuilder<LanguageBloc, Locale?>( // change the state for Locale? cause could be null
builder: (context, lang) { // different name to lang
return MaterialApp(
title: 'Flutter Demo',
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
theme: ThemeData(
primarySwatch: Colors.blue,
),
locale: lang, // here you set the lang
debugShowCheckedModeBanner: false,
home: const HomeScreen(),
);
},
),
);
}
----- EDIT WITH BUTTON -----
I thought you would need a Switch Button that handles the booleans but no, you only need one button that will be the one to change it so I did it this way:
class _HomePageState extends State<HomePage> {
bool _currentLanguageBool = false; // variable to know if is english or french
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LanguageCubit(), // with this we can use the Cubit in all the page, normally you have to have it in main and in the MaterialApp
child: Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
BlocProvider.of<LanguageCubit>(context)
.newLang(_currentLanguageBool);
setState(() {
_currentLanguageBool = !_currentLanguageBool;
}); // with this you change the variable
},
child: Text('Switch lang'),
),
),
),
);
}
}
We will make our widget a StatefulWidget so we can just change the boolean variable and know if it is in English or French. If you don't want to use Stateful let me know, because we can use it with the same Cubit, but it would change the code and a little bit the logic of the LanguageCubit.
It is necessary to pass the localization to the bloc, but the problem is that it is initialized to a lower level, is it possible to somehow swap them? If I could initialize the localization S.delegate first, then it is possible to pass it to the bloc constructor. Is there a solution for this?
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: ConfigProvider(),
),
ChangeNotifierProvider.value(
value: ChannelProvider(),
),
BlocProvider<InternetBloc>(
create: (context) => InternetBloc(ticker: Ticker()),
),
BlocProvider<MultiBloc>(
create: (context) => MultiBloc(ticker: Ticker()),
),
BlocProvider<DbBloc>(
create: (context) => DbBloc(),
),
BlocProvider<FileBloc>(
create: (context) => FileBloc(),
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: StartPage(),
));
}
}
S.delegate taken from the flutter documentation Internationalizing Flutter apps here but in earlier versions of the documentation for some reason AppLocalizations.delegate was named S.delegate it is possible to write less with frequent use for example Text(S.of(context).title) Why do I need internalization inside the bloc because it breaks the architecture? I need some exceptions that occur inside the bloc to be translated into several languages and become understandable to the users.
Bloc is good because it works without context. Internalization requires context. You can try to pass it to the block, but in my case, the bloc is initialized before the internalization. MultiProvider before MaterialApp, how to change the sequence and use internalization inside bloc?
class MyApp extends StatelessWidget {
final routes = <String, WidgetBuilder>{
StartPage.routeName: (BuildContext context) => new StartPage(),
EditorPage.routeName: (BuildContext context) => new EditorPage(),
};
#override
Widget build(BuildContext context) {
//localization always == null
var localization = S.of(context);
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: ChannelModel(),
),
BlocProvider<InternetBloc>(
create: (context) => InternetBloc(localizations: localization),
),
BlocProvider<MultiBloc>(
create: (context) => MultiBloc(),
),
BlocProvider<DbBloc>(
create: (context) => DbBloc(),
),
BlocProvider<FileBloc>(
create: (context) => FileBloc(),
),
],
child: MaterialApp(
localizationsDelegates: [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: StartPage(),
routes: routes,
));
}
}
I'm new to Flutter. I have encountered the following problem where I have no idea how to add another changenotifierprovider to my app.dart. Before that I already have the EntryProvider(), Now I wish to add another provider called EnterProvider(). Below is my coding for app.dart:
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => EntryProvider(),
child: MaterialApp(
home: WelcomeBackPage(),
theme: ThemeData(
accentColor: Colors.orangeAccent,
primaryColor: Colors.black,
textTheme: GoogleFonts.openSansTextTheme(),
),
debugShowCheckedModeBanner: false,),
);
}
}
Could anyone help me out on this?
You can try MultiProvider .
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
When i tried to use localization using provider in flutter it is showing no such method error.
I am also using provider for authentication using google and fb and email authentication but the same time i also need to implement localization in the app at as shown below.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:rewahub/AppLanguage.dart';
import 'package:rewahub/locator.dart';
import 'package:rewahub/models/auth_model.dart';
import 'app_localizations.dart';
import 'views/login_page.dart';
import 'views/main_page.dart';
void main() async{
AppLanguagemodel appLanguagemodel = AppLanguagemodel();
await appLanguagemodel.fetchLocale();
setupLocator();
runApp(MyApp(appLanguagemodel:appLanguagemodel,));
}
class MyApp extends StatelessWidget {
final AppLanguagemodel appLanguagemodel;
MyApp({this.appLanguagemodel});
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
builder: (_) => appLanguagemodel,
child: Consumer<AppLanguagemodel>(builder: (context, appmodel, child) {
return MaterialApp(
//locale: model.fetchLocale(),
locale: appmodel.appLocal,
supportedLocales: [
Locale('en', 'US'),
Locale('ar', ''),
],
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
home: LogInPage(),
);
}),
),
ChangeNotifierProvider(builder: (_) => locator<AuthModel>()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
// initialRoute: '/',
//onGenerateRoute: Router.generateRoute,
home: ScreensController(),
),
);
}
}
class ScreensController extends StatelessWidget {
final StreamController<bool> _verificationNotifier =
#override
Widget build(BuildContext context) {
final user = Provider.of<AuthModel>(context);
switch (user.status) {
case Status.Unauthenticated:
// return LanguageSelect();
case Status.Authenticating:
return LogInPage();
case Status.Authenticated:
return MainPage();
default:
return LogInPage();
}
}
}
it will be like
runApp(MultiProvider(
providers: [
ChangeNotifierProvider.value(value: CurrentData()),
ChangeNotifierProvider.value(value: AppProvider()),
ChangeNotifierProvider.value(value: UserProvider.initialize()),
],
child: Consumer<CurrentData>(
builder: (context, provider, child) => MaterialApp(
debugShowCheckedModeBanner: false,
//title: 'Flutter App Localization with Provider demo',
locale: Provider.of<CurrentData>(context).locale,
home: ScreensController(),
localizationsDelegates: [
const AppLocalizationDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en'),
const Locale('ar'),
const Locale('fr'),
const Locale('es'),
const Locale('ru'),
],
),
),
));