What's the best way to implement requests with tokens clearly? - flutter

I have screen with tabs and each screen implements AutomaticKeepAliveClientMixin. When I navigate to this screen(with tabs), each tab in initState fetches data from server like that:
fetchData()async{
final token = await getToken();//refresh if it is expired.
return fetchData(token);
}
I think it'd better if I initialize data for all the tabs in one request, because I can catch only one refresh token expired and socket exception in single place.
fetchAllData()async{
final token = await getToken();//refresh if it is expired.
return fetchAllData(token);
}
How would you build logic for screen and requests like that? Is my approach is something similar to what you use?

I would recommend you to use a Provider (https://pub.dev/packages/provider). By subscribing to the same Provider, you will be able to reuse the data you've once fetched. For instance, I've used this approach to provide to my App (at different places) the current user:
class UserModel extends ChangeNotifier {
User _currentUser;
void setUser(User user) {
_currentUser = user;
notifyListeners();
}
Future<User> getUser(BuildContext context) async {
if (_currentUser == null) {
_currentUser = await getUserRequest(context, hasRedirect: false);
}
return _currentUser;
}
}
Hope it will fit your needs !

You can add your fetchAllData method to the initState of the widget that holds all of the tabbed widgets. Then, you can you can pass the relevant data to the contructors of each of the tabbed widgets. Not the best solution, but it should work.
I'd still recommend Provider. State management systems are not all inclusive, nor are exclusive. Depending on how your state is presented your could use more than one state management system. Helll, the bloc library already includes the provider library.

Related

Clean Architecture why do we have use cases?

in Clean Architecture we have use cases as business logic rules. but we can also call functions in the repository directly so we don't need use cases.
what are the reasons behind this?
sample use case
class GetMarketUseCase implements UseCase<Stream<ResponseModel>, void> {
final PriceTrackerRepository priceTrackerRepository;
GetMarketUseCase(this.priceTrackerRepository);
#override
Stream<ResponseModel> call(void params) {
return priceTrackerRepository.getMarketWithSymbols();
}
}
sample repository
class PriceTrackerRepositoryImpl implements PriceTrackerRepository {
late final PriceTrackerDataSource priceTrackerDataSource;
PriceTrackerRepositoryImpl(this.priceTrackerDataSource);
#override
Stream<ResponseModel> getMarketWithSymbols() {
return _marketStreamController.stream;
}
Because it prevents your presenter become a God object when it must handle UI logic and buniness logic too.
For example: a logout use case, you need to call API logout inside AuthenRepo, unregister Firebase FCM token, close socket, and maybe clear some local data inside CartRepo, UserRepo, ... then imagine put all those things in Presenter, what a mess instead of create a LogoutUseCase call to every repositories you need
And moreover, you can use it for many places, like when user press Logout button, when user login token is expired, ... just call LogoutUseCase instead of copy code from this Presenter to another Presenter, also make is easy for you when you need to change something about logout requirement
Code example for Presenter is Bloc:
AuthBloc with UseCase:
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthBloc(AuthState state) : super(state) {
on<AuthLogoutEvent>(_onLogout);
}
Future<void> _onLogout(
AuthLogoutEvent event,
Emitter<AuthState> emit,
) async {
await getIt<LogoutUseCase>().call(NoParams());
}
}
AuthBloc without UseCase:
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthBloc(AuthState state) : super(state) {
on<AuthLogoutEvent>(_onLogout);
}
Future<void> _onLogout(
AuthLogoutEvent event,
Emitter<AuthState> emit,
) async {
await getIt<AuthRepo>().logout();
await FirebaseMessaging.instance.deleteToken();
await getIt<SocketRepo>().close();
await getIt<CartRepo>().clearData();
await getIt<UserRepo>().clearData();
// maybe more Repo need to call here :((
}
}
In your example above, it is only simple use case with only action getMarketWithSymbols(), then I agree Usecase here is redundant, but for consistency, it should have and who know, in the future this UseCase need to scale up, then it will easy for you then.
We need a usecase as a kind of intermediate link between presentation and domain layers, to ensure independence of all layers

How to attend best practice for not using UI code in the Controller with GetX flutter when I need to show a Dialog if my task complete.?

For a simple Email login with OTP code I have a structure as follows.
View
await _signUpCntrl.signUp(email, password);
Controller
_showOtpDialog(email);
_showOtpDialog func
return Get.dialog(
AlertDialog(
So the thing is _showOtpDialog function is inside a controller file. ie. /Controllers/controller_file.dart
I want do something like a blocListener, call the _showOtpDialog from a screen(view) file on signup success. (also relocate the _showOtpDialog to a view file)
Using GetX I have to use one of the builders either obs or getbuilder. Which is I think not a good approach to show a dialog box.
On internet it says Workers are the alternative to BlocListener. However Workers function resides on Controller file and with that the dialog is still being called on the controller file.
As OTP dialog will have its own state and a controller I wanted to put it inside a /view/viewfile.dart
How do I obtain this?
I tried using StateMixin but when I call Get.dialog() it throw an error.
visitChildElements() called during build
Unlike BLoC there's no BlocListener or BlocConsumer in GetX.
Instead GetX has RxWorkers. You can store your response object in a Rx variable:
class SomeController extends GetxController{
final response= Rxn<SomeResponse>();
Future<void> someMethod()async{
response.value = await someApiCall();
}
}
And then right before the return of your widget's build method:
class SomeWidget extends StatelessWidget{
final controller = Get.put(SomeController());
#override
Widget build(BuildContext context){
ever(controller.response, (SomeResponse res){
if(res.success){
return Get.dialog(SuccessDialog()); //Or snackbar, or navigate to another page
}
....
});
return UI();
}
First thing, you will need to enhance the quality of your question by making things more clearly. Add the code block and the number list, highlight those and making emphasize texts are bold. Use the code block instead of quote.
Seconds things, Depends on the state management you are using, we will have different approaches:
Bloc (As you already added to the question tag). By using this state management, you controller ( business logic handler) will act like the view model in the MVVM architecture. In terms of that, You will need to emit a state (e.g: Sent success event). Afterward, the UI will listen to the changes and update it value according to the event you have emitted. See this Bloc example
GetX (As your code and question pointed out): GetX will acts a little bit different. you have multiple ways to implement this:
Using callbacks (passed at the start when calling the send otp function)
Declare a general dialog for your application ( this is the most used when it comes to realization) and calling show Dialog from Bloc
Using Rx. You will define a Reactive Variable for e.g final success = RxBool(true). Then the view will listen and update whenever the success changes.
controller.dart
class MyController extends GetxController {
final success = RxBool(false);
void sendOtp() async {
final result = await repository.sendOTP();
success.update((val) => {true});
}
}
view.dart
class MyUI extends GetView<MyController> {
#override
Widget build(BuildContext context) {
ever(controller.success, (bool success) {
// This will update things whenever success is updated
if (success) {
Get.dialog(AlertDialog());
}
});
return Container();
}
}

Flutter: Invoking Provider method in another class outside of the widget tree

I am currently using Provider in my Flutter project to manage the authentication state of my mobile app.
The model for AuthProvider is as follow:
lib/models/auth_provider.dart
class AuthProvider with ChangeNotifier {
// Some methods and properties are removed for simplicity.
// ...
bool loggedIn;
void allowAccess() {
loggedIn = true;
notifyListeners();
}
void revokeAccess() {
loggedIn = false;
notifyListeners();
}
}
The application uses some services from another class to check the validity of the authentication token.
If the the token is not valid anymore, the method in the service in another class will need to revoke the access:
lib/services/auth_services.dart
import 'package:exampleapp/shared/global_context.dart' as global_context;
class AuthService {
// Some methods and properties are removed for simplicity.
// ...
void checkValidity() {
// ...
if(notValid) {
// Use provider to revoke access
Provider.of<AuthProvider>(
global_context.GlobalContext.globalContext!, listen: false)
.revokeAccess();
}
}
}
To achieve this (since there is no context outside of the widget tree), the app uses a global context to allow the services file to invoke the Provider method:
lib/shared/global_context.dart
import 'package:flutter/material.dart';
class GlobalContext {
static BuildContext? globalContext;
}
And add the following line in the build method of the widget that might involves auth state change:
global_context.GlobalContext.globalContext = context;
I read that we're not advised to access Provider outside the widget tree and I don't think using a GlobalContext is the best practice. Is there any other way that I could do this by using Provider?
P/S: I'm still learning Flutter, please comment below if any clarification is needed.
I usually use the get_it package, It gives the ability to call Provider without needing to specifiy a particular context.
https://pub.dev/packages/get_it
First I would call setupLocator()
import 'package:get_it/get_it.dart';
GetIt locator = GetIt.instance;
void setupLocator() {
locator
.registerLazySingleton(() => AuthProvider());
}
Then use it like this..
class SomeClass {
final _provider = locator<AuthProvider>();
void someMethod(){
_provider.revokeAccess();
}
}
This is a very good and important question obviously there are tons of approaches that we can follow here, you can use a global context but you have to make sure it's always the correct context so whenever you push/pop different routes you have to also sync the context
One easy approach i think would be to add the context to checkValidity() function as a parameter
Also i would recommend checking out stacked state management solution https://pub.dev/packages/stacked its perfect for situations like this.

Riverpod - ref.onDispose called when object is created, and not when disposed

I have this ViewModel and a Riverpod provider for it:
final signInViewModelProvider = Provider.autoDispose<SignInViewModel>((ref) {
final vm = SignInViewModel();
ref.onDispose(() {
vm.cleanUp();
});
return vm;
});
class SignInViewModel extends VpViewModelNew {
FormGroup get form => _form;
String get emailKey => _emailKey;
String get passwordKey => _passwordKey;
final String _emailKey = UserSignInFieldKeys.email;
final String _passwordKey = UserSignInFieldKeys.password;
final FormGroup _form = FormGroup({
UserSignInFieldKeys.email:
FormControl<String>(validators: [Validators.required]),
UserSignInFieldKeys.password:
FormControl<String>(validators: [Validators.required])
});
void cleanUp() {
print('cleaning up');
}
void onSubmitPressed(BuildContext context) {
// _saveRegistrationLocallyUseCase.invoke(
// form.control(_self.emailKey).value as String ?? '',
// form.control(_self.passwordKey).value as String ?? '');
}
}
abstract class VpViewModelNew {
VpViewModelNew() {
if (onCreate != null) {
onCreate();
print('creating');
}
}
void onCreate() {}
}
When I navigate to the page that has the signInViewModelProvider, it prints to the console:
flutter: signInPage building
flutter: creating
flutter: cleaning up
Then popping the page from the stack with Navigator.pop() prints nothing.
Then navigating to the page again prints the same 3 lines in the same order.
I expected onDispose to be called after Navigator.pop(), and not when navigating to the page that reads the provider. Why is onDispose being called directly after creation, and not when using Navigator.pop() (when I expected the provider to be disposed of since no other views reference it)?
Edit: I access the provider with final viewModel = context.read<SignInViewModel>(signInViewModelProvider);
I don't need to listen since I don't need to rebuild the page on
change. Is consumer less performant for this?
No, the performance is meaningless, even if it's listening it's not really affecting the performance because as a Provider there is no way to notify (which is not the case with a state notifier or change notifier)
Also if you don't care to listen after the value has been read The auto dispose understand no one is watching it and it disposes, it's better to use context.read when using tap or gestures that modify something
(I realize this is late to the party but maybe it'll help somebody)
The Riverpod docs come out pretty strongly against using read for the reason you said, i.e. performance/rebuilding concerns.
Basically you should always use watch except:
If you want your custom callback function called when it updates (use listen)
If the actual reading is happening asynchronously or in response to user action (like in an onPressed): this is the only time to use read.
If you're having issues with your widgets rebuilding too often, Riverpod has some ways to deal with that that don't involve using read.

Flutter, using a bloc in a bloc

I have two BLoCs.
EstateBloc
EstateTryBloc
My Application basically gets estates from an API and displays them in a similar fashion
Now I wanted to add a sort functionality, but I could only access the List of Estates via a specific state.
if(currentState is PostLoadedState){{
print(currentState.estates);
}
I wanted to make the List of estates available for whichever bloc, that needed that list.
What I did was, I created the EstateTryBloc, which basically contains the List of estates as a state.
class EstateTryBloc extends Bloc<EstateTryEvent, List<Estate>> {
#override
List<Estate> get initialState => [];
#override
Stream<List<Estate>> mapEventToState(
EstateTryEvent event,
) async* {
final currentState = state;
if(event is AddToEstateList){
final estates = await FetchFromEitherSource(currentState.length, 20)
.getDataFromEitherSource();
yield currentState + estates;
}
}
}
As I print the state inside the bloc I get the List of estates but I dont know how I would use that List in a different bloc.
print(EstateTryBloc().state);
simply shows the initialState.
I am open for every kind of answer, feel free to tell me if a different approach would be better.
When you do print(EstateTryBloc().state); you are creating a new instance of EstateTryBloc() that's why you always see the initialState instead of the current state.
For that to work, you must access the reference for the instance that you want to get the states of. Something like:
final EstateTryBloc bloc = EstateTryBloc();
// Use the bloc wherever you want
print(bloc.state);
Right now the recommended way to share data between blocs is to inject one bloc into another and listen for state changes. So in your case it would be something like this:
class EstateTryBloc extends Bloc<EstateTryEvent, List<Estate>> {
final StreamSubscription _subscription;
EstateTryBloc(EstateBloc estateBloc) {
_subscription = estateBloc.listen((PostState state) {
if (state is PostLoadedState) {
print(state.estates);
}
});
}
#override
Future<Function> close() {
_subscription.cancel();
return super.close();
}
}
To be honest I overcomplicated things a little bit and did not recognize the real problem.
It was that I accidently created a new instance of EstateBloc() whenever I pressed on the sort button.
Anyways, thanks for your contribution guys!