Riverpod as global variable - flutter

I'm using riverpod in my project and I want to use is it provide instance of User class between widgets.
My UserState class:
import 'package:caralgo_mobile_app/models/user.dart';
import 'package:flutter/cupertino.dart';
class UserState extends ChangeNotifier{
User? _user;
User? getUser(){
return _user;
}
void setUser(User user){
_user = user;
}
}
Function where I'm writing to _user:
void logIn(){
if(loginFormKey.currentState!.validate()){
final _user = UserDraft(email: emailTEC.text, password: passwordTEC.text);
_apiService.post('/signin', _user.toJson()).then((value) {
scaffoldMessengerKey.currentState!.showSnackBar(SnackBar(content: Text(value!.message)));
ProviderContainer _container = ProviderContainer();
_container.read(userProvider).setUser(User.fromJson(value.data['user']));
_navigatorService.navigateToMainPage();
}
);
}
}
Class where I want to read User data:
class ConfigurationPageViewModel {
final ApiService _apiService = GetIt.I.get<ApiService>();
static final ProviderContainer _container = ProviderContainer();
final registrationFormKey = GlobalKey<FormState>();
final validator = Validator();
final emailTEC = TextEditingController(text: _container.read(userProvider).getUser()!.email);
final nameTEC = TextEditingController(text: _container.read(userProvider).getUser()!.firstName);
final familyNameTEC = TextEditingController(text: _container.read(userProvider).getUser()!.lastName);
final passwordTEC = TextEditingController(text: _container.read(userProvider).getUser()!.password);
var isEnterpriseUser = false;
final enterpriseNameTEC = TextEditingController(text: _container.read(userProvider).getUser()!.enterprise);
}
I assumed this solution can work, instead it throws an error when I'm opening the ConfigurationPage:
Null check operator used on a null value

The problem occurs because of usage of ProviderContainer. In riverpod every provider has two objects: provider and state of provider. Providers itself are global, but states are container depending. In this case I couldn't access the User data, because each container that I used had separate state.
To solve this issue I got rid of ProviderContainers completely.
I'm passing userProvider directly from consumer widget to:
void logIn(dynamic userProvider){
if(loginFormKey.currentState!.validate()){
final _user = UserRegistrationInfo(email: emailTEC.text, password: passwordTEC.text);
_apiService.post('/signin', _user.toJson()).then((value) {
scaffoldMessengerKey.currentState!.showSnackBar(SnackBar(content: Text(value!.message)));
userProvider.setUser(User.fromJson(value.data['user']));
_navigatorService.navigateToMainPage();
}
);
}
}
And all the TextEditControllers are initialized inside the widget:
#override
void initState() {
_configurationPageViewModel.emailTEC.text = ref.read(userProvider).getUser()!.email;
_configurationPageViewModel.nameTEC.text = ref.read(userProvider).getUser()!.firstName;
_configurationPageViewModel.familyNameTEC.text = ref.read(userProvider).getUser()!.lastName;
_configurationPageViewModel.enterpriseCodeTEC.text = ref.read(userProvider).getUser()!.enterprise ?? '';
_configurationPageViewModel.phoneNumberTEC.text = ref.read(userProvider).getUser()!.phone ?? '';
super.initState();
}

Related

How to handle _Future<dynamic> value in flutter?

I am trying to get value from firebase in flutter. during that time, I am receiving _Flutter value returning from the Future<> type returning function. please help someone
I am having a code for fetching values from firebase.. the function gets a value from firebase by querying with an attribute
class FirebaseMethods {
Future<List> findEvents(dynamic attribute, dynamic value) async {
CollectionReference eventCollection =
FirebaseFirestore.instance.collection('events');
return eventCollection
.where(attribute, isEqualTo: value)
.get()
.then((QuerySnapshot querySnapshot) {
List events = [];
querySnapshot.docs.forEach((doc) {
events.add(doc.data());
});
return events;
}).catchError((error) {
print("Failed to retrieve events: $error");
});
}
Future<List> findUsers(dynamic attribute, dynamic value) async {
CollectionReference userCollection =
FirebaseFirestore.instance.collection('profile');
return userCollection
.where(attribute, isEqualTo: value)
.get()
.then((QuerySnapshot querySnapshot) {
List users = [];
querySnapshot.docs.forEach((doc) {
users.add(doc.data());
});
return users;
}).catchError((error) {
print("Failed to retrieve users: $error");
});
}
}
And I am calling the above function 'findUsers' in the following way:
dynamic database_functions = FirebaseMethods();
class RenderProfileView extends StatefulWidget {
String email;
RenderProfileView(this.email, {super.key});
#override
State<RenderProfileView> createState() => _RenderProfileViewState();
}
class _RenderProfileViewState extends State<RenderProfileView> {
TextEditingController name_controller = TextEditingController();
TextEditingController phone_number_controller = TextEditingController();
late dynamic user_json = database_functions.findUser('email', widget.email); // without late I am getting error and getting values with attribute 'email' = widget.email
dynamic get_name() {
print(user_json);
return 'some_value';
}
}
When the 'findUser' function is called, the printing message is -> Instance of '_Future'
Someone please help.. if any other way to solve the issue please mention it.
Future describes async operations in flutter. you must await all Futures results. Either by using the await keyword or .then property.
You could try adding initState to your stateful widget or go with a FutureBuilder depending on your use case.
Below is an edited version of your code.
dynamic database_functions = FirebaseMethods();
class RenderProfileView extends StatefulWidget {
String email;
RenderProfileView(this.email, {super.key});
#override
State<RenderProfileView> createState() => _RenderProfileViewState();
}
class _RenderProfileViewState extends State<RenderProfileView> {
TextEditingController name_controller = TextEditingController();
TextEditingController phone_number_controller = TextEditingController();
late dynamic user_json;
#override
void initState() {
super.initState();
database_functions.findUser('email', widget.email).then((data) {
user_json = data
});
}
String get name => 'some_value';
}

How to Mock the Objectbox in Flutter?

I am writing a bloc test in which I want to test GroupBloc and I need to mock the Objectbox database.
How can i do this?
Objectbox:
class ObjectBox {
/// The Store of this app.
late final Store store;
/// A Box of groups.
late final Box<GroupEntity> groupsBox;
late final Box<LessonEntity> lessonBox;
late final Box<StudentEntity> studentBox;
ObjectBox._create(this.store) {
groupsBox = Box<GroupEntity>(store);
lessonBox = Box<LessonEntity>(store);
studentBox = Box<StudentEntity>(store);
}
/// Create an instance of ObjectBox to use throughout the app.
static Future<ObjectBox> create() async {
final store = await openStore();
return ObjectBox._create(store);
}
}
My Repository:
class GroupRepository {
GroupDao dao = GroupDao();
static BaseConverter<GroupEntity, Group> converter = GroupDbConverter();
Future<List<Group>> getGroupList() async {
final getGroupsList = await dao.getAll();
final groupsList = converter.listInToOut(getGroupsList);
return groupsList;
}
}

Riverpod - State notifier to get current user is always null

I have this state notifier to get the current user:
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final userProvider = StateNotifierProvider<UserNotifier, User?>((ref) {
return UserNotifier(null);
});
class UserNotifier extends StateNotifier<User?> {
UserNotifier(User? state) : super(state) {
FirebaseAuth.instance.authStateChanges().listen((user) {
if (user == null) {
clearUser();
} else {
setUser(user);
}
});
}
void setUser(User user) {
state = user;
}
void clearUser() {
state = null;
}
}
I use it in my Http client to get the id token:
final Provider<Dio> httpClientProvider = Provider<Dio>((ref) {
final dio = Dio(BaseOptions(baseUrl: EnvironmentConstants.of().apiUrl));
final localDataSource = ref.read(localStorageDataSourceProvider);
final user = ref.read(userProvider);
String? accessToken = localDataSource.get<dynamic>(
keyToRead: 'access_token')['access_token_value'] as String?;
accessToken = accessToken ?? user?.getIdToken() as String;
The thing is the value of user is always null in the HttpProvider.
So final user = ref.read(userProvider); always returns null.
FirebaseAuth.instance.authStateChanges().listen( shows it is populated but it is also running too often:
I think this bit is always setting it back to null as it is also running too often:
final userProvider = StateNotifierProvider<UserNotifier, User?>((ref) {
return UserNotifier(null);
});
I'm not sure what I could pass into there instead of null.
Made the instance of the state notifier a global variable so it doesn't re-instantiate it when something calls the ref.read(userProvider):
var currentUser = UserNotifier();
final userProvider = StateNotifierProvider<UserNotifier, User?>((ref) {
return currentUser;
});
Very open to suggestions for improvement as I'm not strictly sure if this is meant to be done... Can I achieve the same outcome without a global variable?

Riverpod, reading state in outside BuildContext and Provider

I am struggling to figure out why this is not working (as opposed to the documentation which states it should be working).
I have a provider something like this
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:putin_flutter_client/api/client.dart';
import 'package:putin_flutter_client/api/storage.dart';
final userProvider = StateNotifierProvider((_) => UserNotifier());
class UserNotifier extends StateNotifier<UserState> {
UserNotifier() : super(UserState());
set username(String username) {
state = UserState(username: username, password: state.password, jwt: state.jwt);
secureStorageWrite('username', username);
}
set password(String password) {
state = UserState(username: state.username, password: password, jwt: state.jwt);
secureStorageWrite('password', password);
}
set jwt(String jwt) {
state = UserState(username: state.username, password: state.password, jwt: jwt);
Client.jwt = jwt;
secureStorageWrite('jwt', jwt);
}
String get jwt {
return state.jwt;
}
Future<void> initState() async {
final user = await UserState.load();
state.username = user.username;
state.password = user.password;
state.jwt = user.jwt;
}
}
class UserState {
String username;
String password;
String jwt;
UserState({
this.username,
this.password,
this.jwt,
});
static Future<UserState> load() async {
return UserState(
username: await secureStorageRead('username'),
password: await secureStorageRead('password'),
jwt: await secureStorageRead('jwt'),
);
}
}
eventually deep in some widget something like this will update the state
// usilizing the setter on the provider to update the state...
user.jwt = data['token'];
now in some other part of the code I manage the http client. This obviously has no access to BuildContext etc. so I do the following to retrieve the jwt value from the stored state.
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:putin_flutter_client/state/user.dart';
class Client extends http.BaseClient {
final http.Client _client = http.Client();
Future<http.StreamedResponse> send(http.BaseRequest request) {
// Get the container as per riverpod documentation
final container = ProviderContainer();
// Access the value through the getter on the provider
final jwt = container.read(userProvider).jwt;
request.headers['user-agent'] = 'myclient::v1.0.0';
request.headers['Content-Type'] = 'application/json';
if (jwt != null) {
request.headers['X-Auth-Token'] = jwt;
}
return _client.send(request);
}
}
This is always null and the UserState is pretty much empty (all members are null).
In the riverpod documentation it says that this should be working
test('counter starts at 0', () {
final container = ProviderContainer();
StateController<int> counter = container.read(counterProvider);
expect(counter.state, 0);
});
Can someone please help me out figure out what is wrong in my example above?
ProviderContainer() create a new instance of your providers it won't get the actual state.
You need to make your client dependent of the user state like this :
final clientProvider = Provider<Client>((ref){
return Client(ref.watch(userProvider.state))
});
class Client extends http.BaseClient {
Client(this._userState);
final UserState _userState;
final http.Client _client = http.Client();
Future<http.StreamedResponse> send(http.BaseRequest request) {
final jwt = _userState.jwt;
request.headers['user-agent'] = 'myclient::v1.0.0';
request.headers['Content-Type'] = 'application/json';
if (jwt != null) {
request.headers['X-Auth-Token'] = jwt;
}
return _client.send(request);
}
}
when your user state will change the client will be re-instancied with new values
If you don't want to re-instancie each time pass the read method instead :
final clientProvider = Provider<Client>((ref){
return Client(ref.read)
});
class Client extends http.BaseClient {
Client(this._reader);
final Reader _reader;
final http.Client _client = http.Client();
Future<http.StreamedResponse> send(http.BaseRequest request) {
final jwt = _reader(userProvider.state).jwt;
request.headers['user-agent'] = 'myclient::v1.0.0';
request.headers['Content-Type'] = 'application/json';
if (jwt != null) {
request.headers['X-Auth-Token'] = jwt;
}
return _client.send(request);
}
}
As #moulte pointed out (really thanks) can access the providers as global variables and independent of context by instantiating outside and injecting it to the widget scope via UncontrolledProviderScope. The important part is to remember to dispose the global provider before the app terminates or it will never really terminate.
Here's an example code
/// file /state/container.dart
import 'package:hooks_riverpod/hooks_riverpod.dart';
final container = ProviderContainer();
/// file /main.dart
void main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyApp createState() => _MyApp();
}
class _MyApp extends State<MyApp> {
#override
void dispose() {
super.dispose();
// disposing the globally self managed container.
container.dispose();
}
#override
Widget build(BuildContext context) {
return UncontrolledProviderScope(container: container,
child: MaterialApp(
// The usual widget tree
);
}
}
/// Somewhere in a file that is not aware of the BuildContext
/// here's how client.dart accesses the provider
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:putin_flutter_client/state/container.dart';
import 'package:putin_flutter_client/state/user.dart';
class Client extends http.BaseClient {
final http.Client _client = http.Client();
Future<http.StreamedResponse> send(http.BaseRequest request) {
// Simply accessing the global container and calling the .read function
var jwt = container.read(userProvider.state).jwt;
request.headers['user-agent'] = 'putin_flutter::v1.0.0';
request.headers['Content-Type'] = 'application/json';
if (jwt != null) {
request.headers['X-Auth-Token'] = jwt;
}
return _client.send(request);
}
}
ProviderContainer() is meant for using RiverPod in Dart. The equivalent in Flutter is ProviderScope(), but that requires access by the widget context chain, similar to the provider package.

Flutter - Translate oninit

Class 1
#override
void initState() {
super.initState();
text =DemoLocalizations.of(context).trans('connection');
}
Class 2 (DemoLocalizations)
Future<bool> load() async {
String data = await rootBundle.loadString('locale/i18n_${locale.languageCode}.json');
Map<String, dynamic> _result = json.decode(data);
this._sentences = new Map();
_result.forEach((String key, dynamic value) {
this._sentences[key] = value.toString();
});
return true;
}
Return
So the question is: How can i load custom string (internazionalization) when screen load (oninit)?
Use didChangeDependencies instead of initState.
It is called once after widget creation and again when the state of DemoLocalizations is changed.