How to use ChangeNotifier with a abstract class in Flutter? - flutter

In my dart application, I wanted to use ChangeNotifier for my authentication class.
I have an abstract class and another class implements the methods.
abstract class AuthBase {
Future<void> signIn(String email, String password);
Future<void> signUp(String email);
Future<void> signOut();
}
class Auth with ChangeNotifier implements AuthBase {
...
}
As you can see ChangeNotifier is not used with the base class.
What I would like to do is use ChangeNotifier with the abstract class. But not sure how to override the methods in Auth class;
#override
void addListener(listener) {
// TODO: implement addListener
}
#override
void dispose() {
// TODO: implement dispose
}
#override
// TODO: implement hasListeners
bool get hasListeners => throw UnimplementedError();
#override
void notifyListeners() {
// TODO: implement notifyListeners
}
#override
void removeListener(listener) {
// TODO: implement removeListener
}
Can someone provide some help with this?

Provider documentation has an example for that case: https://github.com/rrousselGit/provider#can-i-consume-an-interface-and-provide-an-implementation
abstract class ProviderInterface with ChangeNotifier {
...
}
class ProviderImplementation with ChangeNotifier implements ProviderInterface {
...
}
class Foo extends StatelessWidget {
#override
build(context) {
final provider = Provider.of<ProviderInterface>(context);
return ...
}
}
ChangeNotifierProvider<ProviderInterface>(
create: (_) => ProviderImplementation(),
child: Foo(),
),

Related

How to access provider in model class

I have a class which is not a widget , so it doesn't have any context. So is there any way I can access Class with ChangeNotifier within this model class.
Model Class
class MyRouteObserver extends RouteObserver<PageRoute<dynamic>> {
#override
void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
Provider.of<MyCass>(context, listen: false).restore(); // How I can do this ? as I dont have context here
super.didPop(route, previousRoute);
if (previousRoute is PageRoute && route is PageRoute) {
_sendScreenView(previousRoute);
}
}
}
My Class
class MyClass with ChangeNotifier {
void restore() {
.. Some logic
}
}
Add context filed to your class
final BuildContext context;
then add it to constructor
const MyRouteObserver(this.context);
so you can pass build context to your class from your widget class

How do I integrate flutter_bloc with method channels?

I've started using flutter_bloc package instead of redux to try it out, but I'm not entirely sure how I'm going to call flutter bloc events when receiving things from native (Android/iOS). It was easier with redux because in my parent MyApp widget of my main.dart file, I passed in the redux store to a custom class I created, and dispatched methods from the said class (called MethodChannelHandler).
main.dart:
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final Store<AppState> store = Store<AppState>(
// ... redux stuff ...
);
#override
void initState() {
// sauce
MethodChannelHandler(store);
super.initState();
}
}
methodChannelHandler.dart:
class MethodChannelHandler {
Store<AppState> store;
MethodChannelHandler(this.store) {
methodChannel.setMethodCallHandler(_handleMethod);
}
// Handle method calls from native
Future _handleMethod(MethodCall call) async {
if (call.method == A_METHOD) {
store.dispatch("something from native")
}
}
}
NOTE: I'm inept when it comes to programming vocabulary so please, if possible, please give me a small snippet of example code like I have or link me to some GitHub repo I can refer to instead of giving me a block of text I'm probably not going to understand.
In very simple way it's look like this:
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider<SomeBloc>(
create: (_) {
final bloc = SomeBloc(); //Create bloc
MethodChannelHandler(bloc); //Add method handler
return bloc;
},
lazy: false,
child: Text("Content"),
);
}
}
class SomeBloc extends Bloc {
SomeBloc() : super(SomeInitState());
#override
Stream mapEventToState(event) async* {
if (event is SomeEvent) {
//Handle SomeEvent
}
}
}
class MethodChannelHandler {
final SomeBloc someBloc;
MethodChannelHandler(this.someBloc) {
methodChannel.setMethodCallHandler(_handleMethod);
}
// Handle method calls from native
Future _handleMethod(MethodCall call) async {
if (call.method == A_METHOD) {
someBloc.add(SomeEvent("something from native"));
}
}
}

How to use the BLoC library?

I'm trying to figure out the BLoC library, but it gives me headaches.
I'm trying to fetch hotel names from an API. I have a model and a service responsible for contacting the API and fetching the data. However, I don't know how to connect it to the BLoC library.
Once my app starts, I want BLoC to fetch the data from the API and then show it in the app.
Here's my code:
hotel_model.dart
class Hotels {
final List<Hotel> hotels;
Hotels({this.hotels});
factory Hotels.fromJson(Map<String, dynamic> json) {
return Hotels(
hotels: List<Hotel>.from(
json['hotels'].map(
(x) => Hotel.fromJson(x),
),
),
);
}
}
class Hotel {
final String hotelName;
Hotel({this.hotelName});
factory Hotel.fromJson(Map<String, dynamic> json) {
return Hotel(
hotelName: json['name'],
);
}
}
hotel_service.dart
import 'package:http/http.dart' as http;
abstract class DownloadService {
Future<http.Response> fetchHotels();
}
class HotelService extends DownloadService {
#override
Future<http.Response> fetchHotels() {
final Uri uri = Uri.https('services.lastminute.com', 'mobile/stubs/hotels');
return http.get(uri);
}
}
And here's what I did wit the BLoC lib.
hotel_event.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelEvent {}
class OnAppStartEvent extends HotelEvent {}
hotel_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:hotels/services/hotel/hotel_service.dart';
import 'package:meta/meta.dart';
part 'hotel_event.dart';
part 'hotel_state.dart';
class HotelBloc extends Bloc<HotelEvent, HotelState> {
HotelBloc() : super(HotelFinal());
final HotelService hotelService = HotelService();
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
final response = hotelService.fetchHotels();
yield
}
}
}
hotel_state.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotel hotel;
HotelFinal(this.hotel);
Hotel getHotel() {
return hotel;
}
}
First of all add await to this line in your bloc
final response = await hotelService.fetchHotels();
return List<Hotel> from your fetchHotels function
you must have stateful class for your screen and in the initState
you can create your bloc object and call .add method on it
in your build method wrap your widget with BlocBuilder and on builder callback check your bloc state, if the state is HotelFinal return your ui with list of hotels in your state object.
It'll be useful to add another state for your HotelState for when your bloc is fetching the data, and even for when there's an error. e.g;
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotel hotel;
HotelFinal(this.hotel);
Hotel getHotel() {
return hotel;
}
}
class HotelLoading extends HotelState {
HotelLoading();
}
class HotelError extends HotelState {
final String error;
HotelError(this.error);
}
You would want to change your mapEventToState to something like this:
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
yield HotelLoading();
try {
final response = await hotelService.fetchHotels();
// It seems like your service doesn't return an hotel directly, so you'll have to deal with this as it is not part of the question.
final hotel = getYourHotelHereWithTheResponse;
yield HotelFinal(hotel);
} catch (e) {
yield HotelError('something went wrong getting the hotel info');
}
}
}
Lastly, add a widget to your widget tree that adds FetchEvent to your bloc and add a BlocBuilder to react to the change of states. Note that this is very flexible and can be done in many ways, but it is out of the scope of your very broad question, I'm just showing you how to use the library at a minimal:
class MyStatefulWidget extends StatefulWidget {
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
HotelBloc hotelBloc;
#override
void initState() {
hotelBloc = HotelBloc..add(FetchEvent());
super.initState();
}
#override
void dispose() {
hotelBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder(builder: (context, state) {
if(state is HotelLoading) {
// return a widget to deal with loading
}
if(state is HotelFinal) {
// return a widget to deal with success
}
if(state is HotelError) {
// return a widget to deal with error
}
});
}
}

Abstract class with Change Notifier

I would like to use a Change Notifier with my abstract class but do not think I have this set up correctly. Here is what I am doing:
abstract class Foo with ChangeNotifier {
num get barValue;
}
class FooUtil implements Foo {
num _barNum = 0;
num get barVale => _barNum;
// assume this gets called every 10 seconds.
_someMethod(){
_barNum++;
notifyListeners();
}
}
class Main() {
Foo _foo = FooUtil();
Main() {
_foo.addListener(() {
print(_foo.barValue);
});
}
}
I would like Main to be able to use the listener but this does not seem to work. Am I using the ChangeNotifier wrong in this instance? Is there a method that would work better?
You are missing a few overrides to conform to ChangeNotififier:
#override
void addListener(listener) {
// TODO: implement addListener
}
#override
// TODO: implement barValue
num get barValue => throw UnimplementedError();
#override
void dispose() {
// TODO: implement dispose
}
#override
// TODO: implement hasListeners
bool get hasListeners => throw UnimplementedError();
#override
void notifyListeners() {
// TODO: implement notifyListeners
}
#override
void removeListener(listener) {
// TODO: implement removeListener
}
You will have to implement them before you can use the Class like that.

ChangeNotifier mounted equivalent?

I am extract some logic from Stateful Widget to Provider with ChangeNotifier: class Model extends ChangeNotifier {...}
In my Stateful Widget I have:
if (mounted) {
setState(() {});
}
How I can check if Widget is mounted in Model?
For example how I can call:
if (mounted) {
notifyListeners();
}
A simple way is pass 'State' of your Stateful Widget as a parameter to your 'Model'.
Like this:
class Model extends ChangeNotifier {
Model(this.yourState);
YourState yourState;
bool get _isMounted => yourState.mounted;
}
class YourState extends State<YourStatefulWidget> {
Model model;
#override
void initState() {
super.initState();
model = Model(this);
}
#override
Widget build(BuildContext context) {
// your code..
}
}
I think you don't need to check the State is mounted or not. You just need to check the Model has been already disposed. You can override dispose() method in ChangeNotifier:
class Model extends ChangeNotifier {
bool _isDisposed = false;
void run() async {
await Future.delayed(Duration(seconds: 10));
if (!_isDisposed) {
notifyListeners();
}
}
#override
void dispose() {
super.dispose();
_isDisposed = true;
}
}
And don't forget dispose Model when the State is disposed:
class YourState extends State {
Model model;
#override
void initState() {
super.initState();
model = Model();
}
#override
void dispose() {
model?.dispose();
super.dispose();
}
/// Your build code...
}
Or you can use ChangeNotifierProvider in package Provider, it will help you to dispose Model automatically.
class YourState extends State {
Model model;
#override
void initState() {
super.initState();
model = Model();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Model>(
builder: (build) => model,
child: Container(
child: Consumer<Model>(
builder: (context, model, widget) => Text("$model"),
),
),
);
}
}
as long as you wrap your widget with the provider model state
and as it is known once your widget is disposed
the provider model that is wrapping it already get disposed by default
so all you have to do is to define a variable isDisposed and modify the notifyListeners
as below
MyState with ChangeNotifier{
// to indicate whether the state provider is disposed or not
bool _isDisposed = false;
// use the notifyListeners as below
customNotifyListeners(){
if(!_isDisposed){
notifyListeners()
}
}
#override
void dispose() {
super.dispose();
_isDisposed = true;
}
}
Just use a custom ChangeNotifier class.
import 'package:flutter/cupertino.dart';
class CustomChangeNotifier extends ChangeNotifier {
bool isDisposed = false;
#override
void notifyListeners() {
if (!isDisposed) {
super.notifyListeners();
}
}
#override
void dispose() {
isDisposed = true;
super.dispose();
}
}
you can just override notifyListeners like this
class Model extends ChangeNotifier {
#override
void notifyListeners() {
WidgetsBinding.instance.addPostFrameCallback((t) {
print("skip notify after ${t.inMilliseconds}ms");
super.notifyListeners();
});
}
}
no need additional variables / constructor modification