ChangeNotifier on non-widget classes - flutter

We've just started developing our first Flutter app and we are running into a small problem with ChangeNotifier applied to a non-widget class and the disposal of objects of this class. We get exceptions when a future returns after the class has been disposed, we call notifyListeners after the future returns.
I've seen questions suggesting to use the mounted property to check if the object is disposed, but this is only available in widgets. The ChangeNotifier class uses it's own check to see if it's been disposed, but we can't call those methods (and neither should we, I think)
My current idea is that we should use a widget class, and not apply ChangeNotifier on any non-widget class. But I can't find any similar problems, or some documentation telling me best practices/guidelines.
class ExampleClass extends ChangeNotifier {
ExampleClass() {
}
final ExampleService _service = ExampleService();
List<Stuff> stuff;
Future<void> DoStuff() async {
stuff = await _service.getStuff();
notifyListeners();
}
}
We're looking for a way to properly handle the callbacks/returning futures and only call notifyListeners if the object has not been disposed.

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

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.

Handling api response with bloc pattern with rxdart

Im a bit confused on how rxdart works with bloc pattern. This is a code i copied from a youtube channel. Its a bloc that has a method which returns an API response. There's usually a mapEventToState method somewhere in the bloc but this one doesn't. I've added some comments to show what i understand and hope you guys can correct me. Thanks.
Source code: https://github.com/bilguunint/igdb/blob/master/lib/bloc/get_games_bloc.dart
class GetGamesBloc {
final GameRepository _repository = GameRepository(); // defining the api repository
final BehaviorSubject<GameResponse> _subject = BehaviorSubject<GameResponse>();
// defining a behaviour stream which will give only the latest item/data
getGames(int platformId) async {
GameResponse response = await _repository.getGames2(platformId);
_subject.sink.add(response);
}
// this method fetches the api data but not sure why add response to the sink. Isnt sink suppose to be an event? The response is an api json data so it's a stream right ?
dispose() {
_subject.close();
}
//closing the stream when not in use to prevent memory loss
BehaviorSubject<GameResponse> get subject => _subject;
// defining a getter to be used outside the class
}
final getGamesBloc = GetGamesBloc();
// I think this enables us to use the bloc as getGamesBloc ?
The point of BLoC pattern is to separate business logic from views to keep the code clean, readable and testable. The mapEventToState has the responsibility of converting events to state and you can use any other alternatives to do that. In Cubit from bloc package we're defining functions to emit and change state.
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
In the example you provided, It's defining a function which will change the state. So, as far as I know that's correct and it counts as a business logic component.

Load data asynchronously into ChangeNotifier model in Flutter

in my Flutter application I have a widget
class HomeScreen extends StatelessWidget
that uses a model
class HomeScreenModel extends ChangeNotifier
These two objects are tied together using a ChangeNotifierProvider.
When the application loads the HomeScreen widget I would like to call a custom init() function of HomeScreenModel to load asynchronously some data from the disk into the model and then notify the listener with notifyListeners() function. This should be done once.
What's the right place to call this init() function?
As far as I know, for a stateless widget there are no lifecycle functions called only once. I'm pretty sure, though, that the constructor of HomeScreenModel is called only once.
Is it safe to call the async HomeScreenModel.init() function from its own constructor?
Is there any best practice on how to asynchronously load data into a model implemented as a ChangeNotifier?
Thanks to all!
After a bit of searching and tests I choose to call the async init function from the HomeScreenModel constructor. So I have
HomeScreenModel(BuildContext context) {
var initFuture = init(context);
initFuture.then((voidValue) {
_log.d('init finished');
state = HomeScreenModelState.initialized;
notifyListeners();
});
}
and the init function prototype is
Future<void> init(BuildContext context) async
I have found that another way to do this is to use a StatefulWidget and call the async init from the
initState()
function. This function is called only once so is like the ChangeNotifier constructor.
As of now I'm not using StatefulWidgets because it seems to me that they create a sort of strong coupling between ui and business logic. So as of now the above solution seems fine to me.
I hope it can help someone
Another way to call this function would be to put the init() call into the create function of the provider.
runApp(ChangeNotifierProvider(
create: (context) {
var model = TestModel();
model.init();
return model;
},
child: TestApp()));

Purpose of StatefulWidgets in Flutter?

Here is an example of a StatefulWidget. But isn't it just boiler plate code? Why do we need two classes? Normally you copy&paste the extends StatefulWidget part but where is the purpose? Is there some hidden functionality? Or is it some kind of abstraction level? Why was this design chosen by the Flutter team?
class Counter extends StatefulWidget {
int someVar;
Counter(this.someVar);
#override
_CounterState createState() => new _CounterState();
}
And here is the State class. Wouldn't be this class sufficient? Why do we need two classes for a StatefulWidget?
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
#override
Widget build(BuildContext context) {
return new Row(children: <Widget>[
new CounterIncrementor(onPressed: _increment),
new CounterDisplay(count: _counter),
]);
}
}
Because all Widgets's are immutable. This means that your Counter class in this case is immutable and thus also all variables should be final. The State, however, is not mutable.
You can check out the documentation about the Widget class to read more about its purpose.
A widget is an immutable description of part of a user interface. [...] Widgets themselves have no mutable state (all their fields must be final). If you wish to associate mutable state with a widget, consider using a StatefulWidget, which creates a State object (via StatefulWidget.createState) whenever it is inflated into an element and incorporated into the tree.
Dividing a stateful widget in two is a requirement of some of flutter patterns. Widgets being the representation of a pure function you'd find in functional programming.
In fact React, a library from which flutter takes inspiration use two separate objects too. It's fairly obvious when using the following syntax :
class MyComponent extends React.Component<Props, State> {
state: State = {
foo: 42,
}
...
}
Okey, but why is it that State and the Stateful subclass are deeply linked ? Why can't I use any class as State ?
Ah, that's an interesting question ! For those who've used react before, this is something you can do out there. React can use any object as it's state. Flutter being so similar, why can't we too ?
After all, with a bit of tweaks there's nothing preventing the following syntax, not even the #immutable flag :
class MyStateful extends StatefulWidget<Video> {
#override
void dispose() {}
#override
build(BuildContext context) {
return new Text(state.title);
}
}
So why ?
A quote from flutter documentation :
You might wonder why StatefulWidget and State are separate objects. In Flutter, these two types of objects have different life cycles. Widgets are temporary objects, used to construct a presentation of the application in its current state. State objects on the other hand are persistent between calls to build(), allowing them to remember information.
This would mean that we'd have all the lifecycle methods override you can have on State move to the Stateful subclass. Such as initState, but also dispose or didUpdateWidget. Which doesn't make sense considering widgets are just throwable objets that never update.