How to access flutter localization without a context - flutter

I am using flutter localization from their official documentation here, and I am using clean architecture in my project. I want to access the app localization class without a context so I can translate the error messages in the repository file.
Here is an example:
class UserRepository{
Future<Either<Failure, Unit>> logOut() async{
try{
return const Right(unit);
}catch(e){
return const Left(AuthFailure('I want to translate this error based on app`s language'));
}
}
}

Well, since this is not the best practice to do, you can create a custom localizations widget that gets you the Localizations.of(context) but here the context will be obtained by a GlobalKey that you to get from a widget that we can access the Localization widget context with it, which is inside the MaterialApp of course.
For this, you can use the navigatorKey to achieve it:
// somewhere else in your project
GlobalKey<NavigatorState> ourNavigatorKey = GlobalKey<NavigatorState>();
// main.dart
MaterialApp(
navigatorKey: ourNavigatorKey,
//...
Now that you assigned that key, you can create a WithoutContextLocalizations widget:
Localizations withoutContextLocalizations() {
final BuildContext context = ourNavigatorKey.currentContext!;
return Localizations.of<Localizations>(context, Localizations)!;
}
Now from any place instead of using :
Localizations.of(context);
You can use :
WithoutContextLocalizations();

Related

Best practice for passing param Riverpod's providers only once

I just want to build a Provider which asks params only one and inits correctly.
Since I am just passing params only once, I don't prefer to use .family methods.
I prefer to use .autoDispose which considered the better way.
Here my tryouts:
I tried to make my own .init() method. But it's disposing as soon as method called if it's .autodispose() and the widget not started to listen my provider yet (that's expected). Therefore I couldn't consider a safe way to do that.
I tried .overrideWith() method in a widget basis. But it's neither worked nor I am sure that it's best practice.
Here is my simple code:
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
final myString = 'Hey';
#override
Widget build(BuildContext context, WidgetRef ref) {
//Not worked
ProviderContainer(
overrides: [messageProvider.overrideWith(() => ViewModel(myString))]);
return Scaffold(
body: ProviderScope(
//Not worked either
overrides: [messageProvider.overrideWith(() => ViewModel(myString))],
child: Center(
//I just didn't use .when to shorter code
child: Text(ref.watch(messageProvider).value!.counter.toString()),
),
),
);
}
}
final messageProvider = AsyncNotifierProvider.autoDispose<ViewModel, Model>(
() => throw UnimplementedError());
class ViewModel extends AutoDisposeAsyncNotifier<Model> {
final String param;
ViewModel(this.param);
#override
FutureOr<Model> build() {
//Make some fetch with param, (only once!)
return Model(param.length);
}
}
When I run that. It gives UnimplementedError
Waiting your suggestions & fixes. Thanks in advance!
Expected:
Works properly.
#riverpod
ViewModel myViewModel(MyViewModelRef ref, String param){
return ViewModel(param);
}
This is autoDispose by default in Riverpod 2. If you don't want to auto dispose you can use #Riverpod(keepalive:true) instead of #riverpod
If you don't want to pass the param to the provider, you can eliminate it and hardcode the value to the ViewModel, but at that point, if there are no other dependencies, might as well make it a public final variable in some file, since it looks like this is a singleton that never changes so it is questionable what you'd achieve by making it a Riverpod provider.

Is there a way to get context inside getters?

I'm using flutter_localizations to internationalize my app. So i have this class
final Article article;
ArticleViewModel({required this.article});
String get name {
return article.name;
}
String get isGood {
return article.isGoods ? 'Goods' : 'Services';
}
}
Inside the isGood getter, i need to return "Goods" or "Services" translated depending on the language
but i don't have the context to call Applocalization.of(context)
I need a good approach to achieve this wihthout passing a BuildContext as parameter.
You can try my approach, I have used this way for many of my apps. My way is inject BuildContext to a singleton class with get_it, like this:
#singleton
class AppContext {
final navigatorKey = GlobalKey<NavigatorState>();
BuildContext get navigatorContext => navigatorKey.currentState!.context;
}
then at your first time your app run, put the injected navigatorKey to MaterialApp:
MaterialApp(
navigatorKey: getIt<AppContext>().navigatorKey,
)
Then, you can get BuildContext after that everywhere with:
getIt<AppContext>().navigatorContext
You could pass a BuildContext to your ViewModel (like you did with article) and call Applocalization.of(context) inside your getter

Flutter Riverpod call StateProvider within a class that itself is a StateProvider

I have a Class that is being called by Widgets but this Class needs to pull data from another Class. Basically, I am using Riverpod as Dependency Injection, and am unsure if this is "correct" or am I doing it wrong. Here is what I did:
main.dart
var myClass1 = Class1();
final class1Provider = StateProvider((ref) => myClass1);
final class2Provider = StateProvider((ref) => Class2(myClass1));
Is this the recommended way or should I do something else?
FYI this does work;
Widget build(BuildContext context) {
displayData = (ref.watch(class2Provider.notifier).state).getData();
thanks
This is incorrect. You are using riverpod but are mixing it with global variables which trashes the whole point of using a state-management library.
You should create instances inside the StateProvider:
final class1Provider = StateProvider((ref) => Class1());
In order to access the value of one provider in another you need to use ref.watch() method inside the body of the provider:
final class2Provider = StateProvider((ref) {
final myClass1 = ref.watch(class1Provider);
return Class2(myClass1);
});
Finally, to consume a StateProvider you are watching the notifier instead of the state. This will give you an initial value correctly but will not rebuild your UI when the state changes. Instead you should watch the provider directly.
Widget build(BuildContext context) {
final displayData = ref.watch(class2Provider).getData();
}
For more info read the docs thoroughly https://riverpod.dev/docs/getting_started/.

ProviderNotFoundException was thrown

This is the code I'm using
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user == null) {
return LoginPage();
} else {
return MyHomePage();
}
}
}
and the user file has
class User {
final String uid;
User({this.uid});
}
And I am getting this error:
The following ProviderNotFoundException was thrown building Wrapper(dirty):
Error: Could not find the correct Provider above this Wrapper Widget
This likely happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that Wrapper is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.
You need to provide the User object somewhere in a widget above Wrapper
Something like this:
Provider(
create: (_) => User(),
child: Wrapper()
)
https://pub.dev/documentation/provider/latest/

Flutter showDialog with navigator key rather than passing context

Currently its very hectic to show dialog from any layer of code in app just because one has to pass context in it. Hence i thought to pass navigatorKey.currentContext (Navigator key is a global key passed to Material app navigatorKey parameter) to show dialog. But i got the error
"Navigator operation requested with a context that does not include a Navigator.The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget."
The issue is showDialog calls Navigator.of(context) internally and which looks for the navigator ancestor which ofcourse will return null as the navigator is itself the root. Hence it will not find the navigator as ancestor.
Is there a way we can directly pass the navigator state/context to showDialog function to show the dialog? Or is there a more easy way to show Dialog without passing context to it if we want to show it from bloc?
I found a simple solution:
navigatorKey.currentState.overlay.context
I use this in a redux middleware where I keep navigatorKey, and want to show a dialog globally anywhere in the app everytime I dispatch a specific action.
Since this one is merged:
https://github.com/flutter/flutter/pull/58259
You can use:
navigatorKey.currentContext;
You can make use of InheritedWidget here. Make a InheritedWidget the root for your application which holds a navigator key. Then you can pass any context of child widgets to get the current navigator state.
Example:
InheritedWidget:
// Your InheritedWidget
class NavigatorStateFromKeyOrContext extends InheritedWidget {
const NavigatorStateFromKeyOrContext({
Key key,
#required this.navigatorKey,
#required Widget child,
}) : super(key: key, child: child);
final GlobalKey<NavigatorState> navigatorKey;
static GlobalKey<NavigatorState> getKey(BuildContext context) {
final NavigatorStateFromKeyOrContext provider =
context.inheritFromWidgetOfExactType(NavigatorStateFromKeyOrContext);
return provider.navigatorKey;
}
static NavigatorState of(BuildContext context) {
NavigatorState state;
try {
state = Navigator.of(context);
} catch (e) {
// Assertion error thrown in debug mode, in release mode no errors are thrown
print(e);
}
if (state != null) {
// state can be null when context does not include a Navigator in release mode
return state;
}
final NavigatorStateFromKeyOrContext provider =
context.inheritFromWidgetOfExactType(NavigatorStateFromKeyOrContext);
return provider.navigatorKey?.currentState;
}
#override
bool updateShouldNotify(NavigatorStateFromKeyOrContext oldWidget) {
return navigatorKey != oldWidget.navigatorKey;
}
}
HomeScreen:
// Your home screen
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: NavigatorStateFromKeyOrContext.getKey(context),
home: InitPage(),
);
}
}
The root of the application will look like,
final GlobalKey navigator = GlobalKey<NavigatorState>(debugLabel: 'AppNavigator');
runApp(
NavigatorStateFromKeyOrContext(
navigatorKey: navigator,
child: HomePage(),
),
);
Now from anywhere in the app, pass any context to get the NavigatorState like
NavigatorStateFromKeyOrContext.of(context)
Note: This is one approach I came up with where I used InheritedWidget, there are many other ways to achieve the same, like using Singleton, having a global bloc to provide navigator key, storing the navigator key in a Redux store or any other global state management solutions, etc.
Hope this helps!
Currently, I am showing a dialog by creating a function in my util class which takes the context as a parameter.
static void showAlertDialog(String title, String message, BuildContext context) {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
title: new Text(title),
content: new Text(message),
actions: <Widget>[
// usually buttons at the bottom of the dialog
new FlatButton(
child: new Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
Using the above function as:
UtilClass. showAlertDialog("Title", "Message", context);