How to access provider in model class - flutter

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

Related

Access state from ui without an if statement

am following this Bloc's official example and I couldn't find a way how to access the state without that if statement.
Let's have the example below, I would like to display a specific text based on the initial value of showText, the only possible solution to access the state is via:
if(statement is ExampleInitial) {state.showText? return Text("yes") : return Text("no")}
But am finding this solution hard to implement when you have more values with initial values. Or am I doing this wrong?
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
////////// event
part of 'example_bloc.dart';
abstract class ExampleEvent extends Equatable {
const ExampleEvent();
}
class ExampleStarted extends ExampleEvent {
#override
List<Object> get props => [];
}
////////// state
part of 'example_bloc.dart';
abstract class ExampleState extends Equatable {
const ExampleState();
}
////////// state
class ExampleInitial extends ExampleState {
final bool showText = false;
const ExampleInitial();
#override
List<Object> get props => [showText];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(
builder: (context, state) {
return state.showText ? Text("yes") :Text("no"); // can't access to state.showText
});
}
}
You can declare a variable inside Bloc Class which will be global and need to be set inside the 'bloc.dart' file like in the case of Provider Package. This variable does not need state to be checked before accessing it in UI. You can access this value from the Navigation tree using context.
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
bool showText = false;
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider.of<ExampleBloc>(context).showText
? const Text('Yes')
: const Text('No');
}
}
There is another way in which you declare abstract State Class to always have the boolean value. So, whatever new class extends those State will have inherited boolean value from parent class. This concept is called inheritance in OOP.
////////// state
abstract class ExampleState extends Equatable {
const ExampleState();
final bool showText = false;
}
////////// state
class ExampleInitial extends ExampleState {
const ExampleInitial();
// You can also set ExampleInitial to accept showText and send it to its
// parent class using 'super' method in constructor,
// if parent class has constructor with 'showText' as boolean
#override
List<Object> get props => [];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(builder: (context, state) {
return state.showText ? const Text("yes") : const Text("no");
});
}
}
A pragmatic usecase for different State Classes having different state variables is as follows:
Let's account for three states while fetching data from api
-if(state is DataLoadingState),
// there is no need for state
-if(state is DataLoadedState)
// state need to have a variable named weatherData containing temperatures, cities and so on.
-if(state is ErrorWhileLoadingState)
// state needs to have a reason for the error. For example: errorMsg: 'Internal Server Error'
So, you need to check the state before accessing its values.

Flutter - How to retrieve an implemented class with Get.find after registered with Get.put using GetX?

I have a class called GetConnectApiHelper that implemented an abstraction called IApiHelper, I need to register this class with Get.put inside Bindings and retrieve the implementation inside an abstraction variable but when I try to do that I get an error about "the abstraction is not registered".
How can I inject the dependency correctly making it easy to change in case I need to replace with http, dio etc...(clean architecture)
abstract class IApiHelper {}
class GetConnectApiHelper extends GetxService implements IApiHelper {}
class SignInBinding extends Bindings {
#override
void dependencies() {
Get.put(GetConnectApiHelper());
Get.put(SignInController());
}
}
class SignInController extends GetxController {
final IApiHelper apiHelper = Get.find(); // This throws the exception
}
======== Exception caught by widgets library =======================================================
The following message was thrown building Builder(dirty):
"IApiHelper" not found. You need to call "Get.put(IApiHelper())" or "Get.lazyPut(()=>IApiHelper())"
I found a solution. I can set the Interface as a Type and then register the implementation I want to be retrieved.
class SignInBinding extends Bindings {
#override
void dependencies() {
Get.put<IApiHelper>(GetConnectApiHelper());
Get.put(SignInController());
}
}
class SignInController extends GetxController {
final IApiHelper apiHelper = Get.find();
}
print(apiHelper.runtimeType); // it prints Instance of 'GetConnectApiHelper'
Or I can inject the implementation.
class SignInBinding extends Bindings {
#override
void dependencies() {
Get.put<IApiHelper>(GetConnectApiHelper());
Get.put(SignInController(apiHelper: Get.find()));
}
}
class SignInController extends GetxController {
final IApiHelper apiHelper;
SignInController({required this.apiHelper})
}
GetX finds its dependencies based on its exact types so you need to use Get.find<GetConnectApiHelper>()
updated:
class SignInBinding extends Bindings {
#override
void dependencies() {
Get.put(GetConnectApiHelper());
Get.put(SignInController<GetConnectApiHelper>());
}
}
class SignInController<T extends IApiHelper> extends GetxController {
final IApiHelper apiHelper = Get.find<T>();
}

Error: Type argument 'T' doesn't conform to the bound 'Object' of the type variable 'T' on 'GetIt.call'. After migrating to Null Safety

I'm in the process of migrating over a large project to null safety and I'm coming across a strange error I'm not entirely sure how to fix.
"Error: Type argument 'T' doesn't conform to the bound 'Object' of the type variable 'T' on 'GetIt.call'."
class BaseView<T extends BaseProvider?> extends StatefulWidget {
final Widget Function(BuildContext context, T value, Widget? child)? builder;
final Function(T)? onModelReady;
BaseView({this.builder, this.onModelReady});
#override
_BaseViewState<T> createState() => _BaseViewState<T>();
}
class _BaseViewState<T extends BaseProvider?> extends State<BaseView<T?>> {
T model = locator<T>(); <---- This is throwing it
#override
void initState() {
if (widget.onModelReady != null) {
widget.onModelReady!(model);
}
super.initState();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T?>(
create: (context) => model,
child: Consumer<T>(builder: widget.builder!),
);
}
}
I can't find much info on this error and so far any method I've tried hasn't worked out. Can anyone be of assistance?
I'm using Provider for state management and BaseView is what wraps all my other views during build; e.g.:
class EquipmentMainView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BaseView<EquipmentProvider>(
onModelReady: (model) async {
model.getAllFunctions();
},..
Posting here for anyone else that might eventually run across this in the future, just changed the nullability of BaseProvider suggested by jamesdlin by changing
class BaseView<T extends BaseProvider?>
to
class BaseView<T extends BaseProvider>
I had similar issues when I upgraded to flutter 2.0 I made the generic methods in Generic class to explicitly extends Base class Object i.e
from:
import 'package:get_it/get_it.dart';
class PoultryBabaRegistry<T> {
static GetIt _getIt = GetIt.instance;
static void register<T>(T model) {
_getIt.registerSingleton<T extends Object >(model, signalsReady: true);
}
static void remove<T>(T model) {
_getIt.unregister<T extends Object>(instance:model);
}
static T getIt<T>() {
return _getIt.get<T>();
}
}
to:
class PoultryBabaRegistry<T extends Object> {
static GetIt _getIt = GetIt.instance;
static void register<T extends Object>(T model) {
_getIt.registerSingleton<T >(model, signalsReady: true);
}
static void remove<T extends Object>(T model) {
_getIt.unregister<T>(instance:model);
}
static T getIt<T extends Object>() {
return _getIt.get<T>();
}
}

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
}
});
}
}

How to use ChangeNotifier with a abstract class in 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(),
),