Flutter api login using riverpod - flutter

I'm trying to use riverpod for login with a laravel backend. Right now I'm just returning true or false from the repository. I've set a form that accepts email and password. The isLoading variable is just to show a circle indicator. I've run the code and it works but not sure if I'm using riverpod correctly. Is there a better way to do it ?
auth_provider.dart
class Auth{
final bool isLogin;
Auth(this.isLogin);
}
class AuthNotifier extends StateNotifier<Auth>{
AuthNotifier() : super(Auth(false));
void isLogin(bool data){
state = new Auth(data);
}
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier());
auth_repository.dart
class AuthRepository{
static String url = "http://10.0.2.2:8000/api/";
final Dio _dio = Dio();
Future<bool> login(data) async {
try {
Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
return true;
} catch (error) {
return false;
}
}
}
login_screen.dart
void login() async{
if(formKey.currentState.validate()){
setState((){this.isLoading = true;});
var data = {
'email':this.email,
'password':this.password,
'device_name':'mobile_phone'
};
var result = await AuthRepository().login(data);
if(result){
context.read(authProvider).isLogin(true);
setState((){this.isLoading = false;});
}else
setState((){this.isLoading = false;});
}
}

Since I'm not coming from mobile background and just recently use flutter+riverpod in my recent project, I cannot say this is the best practice. But there are some points I'd like to note:
Use interface such IAuthRepository for repository. Riverpod can act as a dependency injection.
final authRepository = Provider<IAuthRepository>((ref) => AuthRepository());
Build data to send in repository. You should separate presentation, business logic, and explicit implementation for external resource if possible.
Future<bool> login(String email, String password) async {
try {
var data = {
'email': email,
'password': password,
'device_name':'mobile_phone'
};
Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
return true;
} catch (error) {
return false;
}
}
Do not call repository directly from presentation/screen. You can use the provider for your logic, which call the repository
class AuthNotifier extends StateNotifier<Auth>{
final ProviderReference ref;
IAuthRepository _authRepository;
AuthNotifier(this.ref) : super(Auth(false)) {
_authRepository = ref.watch(authRepository);
}
Future<void> login(String email, String password) async {
final loginResult = await_authRepository.login(email, password);
state = Auth(loginResult);
}
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier(ref));
On screen, you can call provider's login method
login() {
context.read(authProvider).login(this.email, this.password);
}
Use Consumer or ConsumerWidget to watch the state and decide what to build.
It also helps that instead of Auth with isLogin for the state, you can create some other state. At the very least, I usually create an abstract BaseAuthState, which derives to AuthInitialState, AuthLoadingState, AuthLoginState, AuthErrorState, etc.
class AuthNotifier extends StateNotifier<BaseAuthState>{
...
AuthNotifier(this.ref) : super(AuthInitialState()) { ... }
...
}
Consumer(builder: (context, watch, child) {
final state = watch(authProvider.state);
if (state is AuthLoginState) ...
else if (state is AuthLoadingState) ...
...
})

Instead of using a bool, I like to use enums or class for auth state
enum AuthState { initialize, authenticated, unauthenticated }
and for login state
enum LoginStatus { initialize, loading, success, failed }

Related

How to throw error inside riverpod future provider and catch it on error flutter

final loginProvider =
FutureProvider.family<bool, LoginParam>((ref, param) async {
if (param.sgId == '' || param.password == '') {
return false;
}
final http.Response response =
await APIClient().login(param.sgId, param.password);
if (response.statusCode == 200) {
await APIClient().saveTokens(response);
UserDefaultEntity entity =
await ref.watch(userDefaultsProvider(param.sgId).future);
//ref.state = AsyncValue.data(true);
return true;
} else {
throw Exception(jsonDecode(response.body)['message'] ?? 'Unknown Error');
}
});
void login(String userName, String password) async {
state = AsyncValue.loading();
AsyncValue<bool> result;
try {
result = await ref.refresh(loginProvider(LoginParam(userName, password)));
state = result;
} catch (e) {
state = AsyncError(e);
}
}
I'm trying to throw an custom exception inside riverpod future provider and catch the exception in other state notifier classes, but the catch block is not triggered.
Is there any other way to handle exceptions that future provider throw.
First of all, you won't have to manually catch errors inside a FutureProvider, it will do that for you. Refer this example.
Generally, the operations that happen after certain "user interaction" like a button click (in this case, login operation), are not meant to be written in FutureProvider. Scenarios where you'd be using FutureProvider are as follows:
Fetching some data over HTTP/HTTPS.
Performing operations like reading a file or a local database.
So your use case of login can be achieved using a StateNotifier.
// auth_provider.dart
import 'package:hooks_riverpod/hooks_riverpod.dart';
// Always prefer some strongly typed object to
// know current status of authentication.
enum AuthState {
unauthenticated,
authenticated,
authenticating,
failed,
}
// StateNotifier is recommended to encapsulate all your business
// logic into a single class and use it from there.
class AuthStateNotifier extends StateNotifier<AuthState> {
// Initialize with the default state of "unauthenticated".
const AuthStateNotifier() : super(AuthState.unauthenticated);
Future<void> login(LoginParam params) async {
if (param.sgId.isEmpty || param.password.isEmpty) {
state = AuthState.failed;
return;
}
final http.Response response = await APIClient().login(param.sgId, param.password);
if (response.statusCode == 200) {
await APIClient().saveTokens(response);
UserDefaultEntity entity = await ref.watch(userDefaultsProvider(param.sgId).future);
state = AuthState.authenticated;
return;
} else {
state = AuthState.failed;
throw Exception(jsonDecode(response.body)['message'] ?? 'Unknown Error');
}
}
}
// Finally, create a provider that can be consumed in the presentation layer (UI).
final authProvider = StateNotifierProvider<AuthStateNotifier, AuthState>((ref) => const AuthStateNotifier());
Then, in your UI part, usually in the onTap / onPressed event handler of button, you can use it as follows. Please note that, we have created a button widget that extends the ConsumerWidget to access the ref.
// login.dart
import 'auth_provider.dart';
class LoginButton extends ConsumerWidget {
final LoginParam params;
const LoginButton({
Key? key,
required this.params,
}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
void login() {
try {
await ref.read(authProvider.notifier).login(params);
} catch (e) {
// Handle error here.
}
}
return ElevatedButton(
child: Text('Login'),
// Call the handler here.
onPressed: login,
);
}
}

How to save data of type bool in shared_preferences flutter

I created a separate calss page to working with shared preferences from all the different application pages. Save or edit data. I can save String data with ease, but I am facing a problem saving data of type bool. I try to save data of type bool to store the status of the user logged in or not. I searched for solutions for a long time, but couldn't find.
full code:
import 'package:shared_preferences/shared_preferences.dart';
class MyPreferences {
static const ID = "id";
static const STATE = "state";
static final MyPreferences instance = MyPreferences._internal();
static SharedPreferences _sharedPreferences;
String id = "";
String state = "";
MyPreferences._internal() {}
factory MyPreferences() => instance;
Future<SharedPreferences> get preferences async {
if (_sharedPreferences != null) {
return _sharedPreferences;
} else {
_sharedPreferences = await SharedPreferences.getInstance();
state = _sharedPreferences.getString(STATE);
id = _sharedPreferences.getString(ID);
return _sharedPreferences;
}
}
Future<bool> commit() async {
await _sharedPreferences.setString(STATE, state);
await _sharedPreferences.setString(ID, id);
}
Future<MyPreferences> init() async {
_sharedPreferences = await preferences;
return this;
}
}
Can somebody help me to make bool data.
thank you
Just add a couple methods to your class.
void updateLoggedIn(bool value) {
_sharedPreferences.setBool('logged_in', value);
}
bool isLoggedIn() => _sharedPreferences.getBool('logged_in') ?? false;
Then on login just run
MyPreferences.instance.updateLoggedIn(true)
And the same thing passing in false on logout.
Then whenever you want to check logged in status just run
if(MyPreferences.instance.isLoggedIn()) {
// whatever needs to happen
}

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 Riverpod : How to Implement FutureProvider?

I using Flutter Riverpod package to handling http request. I have simple Http get request to show all user from server, and i using manage it using FutureProvider from Flutter Riverpod package.
API
class UserGoogleApi {
Future<List<UserGoogleModel>> getAllUser() async {
final result = await reusableRequestServer.requestServer(() async {
final response =
await http.get('${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getAllUser');
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
}
User Provider
class UserProvider extends StateNotifier<UserGoogleModel> {
UserProvider([UserGoogleModel state]) : super(UserGoogleModel());
Future<UserGoogleModel> searchUserByIdOrEmail({
String idUser,
String emailuser,
String idOrEmail = 'email_user',
}) async {
final result = await _userGoogleApi.getUserByIdOrEmail(
idUser: idUser,
emailUser: emailuser,
idOrEmail: idOrEmail,
);
UserGoogleModel temp;
for (var item in result) {
temp = item;
}
state = UserGoogleModel(
idUser: temp.idUser,
createdDate: temp.createdDate,
emailUser: temp.emailUser,
imageUser: temp.emailUser,
nameUser: temp.nameUser,
tokenFcm: temp.tokenFcm,
listUser: state.listUser,
);
return temp;
}
Future<List<UserGoogleModel>> showAllUser() async {
final result = await _userGoogleApi.getAllUser();
state.listUser = result;
return result;
}
}
final userProvider = StateNotifierProvider((ref) => UserProvider());
final showAllUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.showAllUser();
return result;
});
After that setup, i simply can call showAllUser like this :
Consumer((ctx, read) {
final provider = read(showAllUser);
return provider.when(
data: (value) {
return ListView.builder(
itemCount: value.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
final result = value[index];
return Text(result.nameUser);
},
);
},
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error $error'),
);
}),
it's no problem if http request don't have required parameter, but i got problem if my http request required parameter. I don't know how to handle this.
Let's say , i have another http get to show specific user from id user or email user. Then API look like :
API
Future<List<UserGoogleModel>> getUserByIdOrEmail({
#required String idUser,
#required String emailUser,
#required String idOrEmail,
}) async {
final result = await reusableRequestServer.requestServer(() async {
final baseUrl =
'${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getUserByIdOrEmail';
final chooseURL = idOrEmail == 'id_user'
? '$baseUrl?id_or_email=$idOrEmail&id_user=$idUser'
: '$baseUrl?id_or_email=$idOrEmail&email_user=$emailUser';
final response = await http.get(chooseURL);
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
User Provider
final showSpecificUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.searchUserByIdOrEmail(
idOrEmail: 'id_user',
idUser: usrProvider.state.idUser, // => warning on "state"
);
return result;
});
When i access idUser from userProvider using usrProvider.state.idUser , i got this warning.
The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'.
It's similiar problem with my question on this, but on that problem i already know to solved using read(userProvider.state) , but in FutureProvider i can't achieved same result using ref(userProvider).
I missed something ?
Warning: This is not a long-term solution
Assuming that your FutureProvider is being properly disposed after each use that should be a suitable workaround until the new changes to Riverpod are live. I did a quick test to see and it does work. Make sure you define a getter like this and don't override the default defined by StateNotifier.
class A extends StateNotifier<B> {
...
static final provider = StateNotifierProvider((ref) => A());
getState() => state;
...
}
final provider = FutureProvider.autoDispose((ref) async {
final a = ref.read(A.provider);
final t = a.getState();
print(t);
});
Not ideal but seems like a fine workaround. I believe the intention of state being inaccessible outside is to ensure state manipulations are handled by the StateNotifier itself, so using a getter in the meantime wouldn't be the end of the world.

Is there any way we can inject provider dependancy to normal class in flutter?

I have one normal dart class in which I want to provide two provider dependency.
So I can access that class though out of my application. I can pass that dependency from the build method of the widget and then I can use this class but I don't want to do that, like 100 times I have to pass that dependency if I used that class 100 times.
I also want to use this class from every lifecycle of flutter as it's generating different types of events for application.
I also want to initialize genrateUserProerties() method only once when the dependency is ready and when the user opens the application.
This is initialized before any provider initializes and it's not always used from the method where we have context available.
I need a way to provide that dependency in a way we can initialize genrateUserProerties() only once.
User _user; and BrandCofiguration _activeBrand; I need these two be pass here when it's ready.
User _user; and BrandCofiguration _activeBrand; both are coming from two different Providers when I received a valid response from the server.
class FireBaseAnalyticsBase {
static FirebaseAnalytics _analytics;
static FirebaseAnalyticsObserver _observer;
**User _user;**
BuildContext _context;
**BrandCofiguration _activeBrand;**
int _seconds;
Stopwatch _stopwatch;
String _eventName;
Map<String, dynamic> _userProperties = {};
bool _isTimeTrackEvent;
FireBaseAnalyticsBase(BuildContext context, UserProvider userProvider,
BrandSelectionProvider brandSelectionProvider) {
this._context = context;
_analytics = FirebaseAnalytics();
_observer = FirebaseAnalyticsObserver(analytics: _analytics);
_activeBrand = brandSelectionProvider.activeBrand;
_user = userProvider.authenticatedUser;
if (_user != null) {
genrateUserProerties();
}
}
void startFirebaseEventWithoutTime(String eventName) {
this._eventName = eventName;
_isTimeTrackEvent = false;
logFirebaseEvent();
}
void startFireBaseEventWithTime(String eventName) {
_stopwatch = Stopwatch();
_stopwatch.start();
_isTimeTrackEvent = true;
_eventName = eventName;
}
void stopFireBaseTimeEvent() {
_stopwatch.stop();
_seconds = (_stopwatch.elapsedMilliseconds / 1000) as int;
_stopwatch.reset();
logFirebaseEvent();
}
Future<void> logFirebaseEvent() async {
if (_isTimeTrackEvent) {
_userProperties
.addAll({FirebaseAnalyticsEnum.time_spent.value: _seconds});
}
print("firebase test");
await _analytics.logEvent(
name: _eventName,
parameters: _userProperties,
);
}
Future<void> genrateUserProerties() async {
print("firebase properties initilize");
var _packageInfo = await PackageInfo.fromPlatform();
_userProperties = {
FirebaseAnalyticsEnum.user_id.value: _user.id.toString(),
FirebaseAnalyticsEnum.platform.value: Platform.operatingSystem,
FirebaseAnalyticsEnum.device_language.value:
Localizations.localeOf(_context).languageCode,
FirebaseAnalyticsEnum.application.value: _packageInfo.appName,
FirebaseAnalyticsEnum.current_api.value: Config.CURRENT_API,
FirebaseAnalyticsEnum.device_type.value: _user.id.toString(),
FirebaseAnalyticsEnum.app_version.value: Config.CURRENT_VERSION,
FirebaseAnalyticsEnum.is_admin.value: _user.isAdmin,
FirebaseAnalyticsEnum.is_educator.value: _user.educator,
FirebaseAnalyticsEnum.is_brand_ambassador.value: _user.brandAmbassador,
FirebaseAnalyticsEnum.salon_role.value: _user.salongroup,
FirebaseAnalyticsEnum.brand.value: _activeBrand.brandName,
FirebaseAnalyticsEnum.school_role.value: _user.schoolgroup,
};
}
}
I think you should use Singleton pattern for these classes consume a lot of resources.
Example:
static FireBaseAnalyticsBase _instance;
static FireBaseAnalyticsBase getInstance(BuildContext context, UserProvider userProvider,
BrandSelectionProvider brandSelectionProvider){
if(_instance == null){
_instance = FireBaseAnalyticsBase(context,userProvider,brandSelectionProvider);
}
return _instance;
}
Or if you passing BuildContext you can get Provider
Provider.of<>(context) in FireBaseAnalyticsBase's Constructor