Trying to study bloc in flutter. I have bloc, events and repository files. I know how to pass data between pages, but i can't pass the parameter to the repository
I need to pass a parameter to the repository for make query to db. For example category id
products_by_category_repository.dart
import 'package:billfort/models/products_model.dart';
import 'package:billfort/strings/strings.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
abstract class ProductsByCategoryRepository {
Future<List<HydraMember>> getProducts();
}
class ProductsByCategoryRepositoryImpl implements ProductsByCategoryRepository {
#override
Future<List<HydraMember>> getProducts() async {
var response = await http.get(Uri.parse("${AppStrings.api}products?category=here need pass id"));
if (response.statusCode == 200) {
var data = json.decode(response.body);
List<HydraMember> products = Products.fromJson(data).hydraMember;
return products;
} else {
throw Exception();
}
}
}
products_bloc.dart
import 'package:bloc/bloc.dart';
import 'package:billfort/bloc/products_event.dart';
import 'package:billfort/bloc/products_state.dart';
import 'package:billfort/models/products_model.dart';
import 'package:billfort/repository/products_repository.dart';
import 'package:meta/meta.dart';
class ProductsBloc extends Bloc < ProductsEvent, ProductsState > {
ProductsRepository repository;
ProductsBloc({
#required this.repository
}): super(null);
#override
// TODO: implement initialState
ProductsState get initialState => ProductsInitialState();
#override
Stream < ProductsState > mapEventToState(ProductsEvent event) async *{
if (event is FetchProductsEvent) {
yield ProductsLoadingState();
try {
List < HydraMember > products = await repository.getProducts();
yield ProductsLoadedState(products: products);
} catch (e) {
yield ProductsErrorState(message: e.toString());
}
}
}
}
If your process of fetching products needs input, then your class FetchProductsEvent should have a property that provides that input, just as your state class ProductsLoadedState for example has a products property that provides the output.
That said, did you upgrade your bloc package without really understanding the changes? Why are you passing null to your base constructor, but still providing an overload for initialState? Your base constructor should get ProductsInitialState() directly and you should remove the overload. If it does not fire warnings for you, you may want to crank up your compiler settings, so your compiler can guide you better.
Related
I need to know wether NFC is active or inactive inside the application. I'm using nfc_manager package and riverpod for state management. Here is my providers and ChangeNotifier class:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:nfc_manager/nfc_manager.dart';
final nfcProvider = ChangeNotifierProvider.autoDispose<NfcService>((ref) {
return NfcService();
});
final nfcIsAvabilableProvider = StreamProvider.autoDispose<bool>((ref) {
ref.maintainState = true;
final stream = ref.watch(nfcProvider).isAvailable.stream;
return stream;
});
class NfcService extends ChangeNotifier {
NfcManager nfcManager = NfcManager.instance;
StreamController<bool> isAvailable = StreamController.broadcast();
NfcService() {
init();
}
init() async {
isAvailable.addStream(nfcManager.isAvailable().asStream());
}
}
When I close the NFC inside the settings, nfcManager.isAvailable() does not return the new state of NFC availability because it is a future not a stream.
So my question is: how to listen to changes on a future object in Flutter?
I am trying to create a persistent interface which forks db calls to floor or another self made web db static store.
Anyway...
The interface part is looking like this:
peristent_interface.dart
import 'package:flutter/material.dart';
import 'package:mwork/database/floor/entities/map_location_entity.dart';
import 'package:mwork/database/floor/result/map_location_result.dart';
import 'persistent_stub.dart'
if(dart.library.io) 'persistent_native.dart'
if(dart.library.js) 'persistent_web.dart';
abstract class Persistent extends ChangeNotifier {
static Persistent? _instance;
static Persistent? get instance{
_instance ??= getPersistent();
return _instance;
}
Future<List<MapLocationResult?>?> getMapLocations();
Future<MapLocationResult?> getMapLocation({int id});
Future<void> insertReplaceMapLocation(MapLocation mapLocation);
Future<void> insertReplaceMapLocations(List<MapLocation> mapLocations);
}
All seems nice so far, but the trouble appears when the init() function below returns Future<AppDatabase> not AppDatabase as I want.
persistent_native.dart
import 'package:floor/floor.dart';
import 'package:mwork/database/floor/database/database.dart';
import 'package:mwork/database/floor/entities/map_location_entity.dart';
import 'package:mwork/database/floor/result/map_location_result.dart';
import 'package:mwork/services/persistent/persistent_interface.dart';
import 'package:mwork/common/m_work_config.dart' as m_work_config;
Persistent getPersistent() => PersistentNative();
class PersistentNative extends Persistent {
final AppDatabase _appDatabase = init(); //<-- Fails here !!
static Future<AppDatabase> init() async {
return await $FloorAppDatabase.databaseBuilder(m_work_config.mWorkFloorDb).build();
}
#override
Future<List<MapLocationResult?>?> getMapLocations() async {
return await _appDatabase.mapLocationDao.getMapLocations();
}
#override
Future<MapLocationResult?> getMapLocation({int id=-1}) async {
return await _appDatabase.mapLocationDao.getMapLocation(id);
}
#override
Future<void> insertReplaceMapLocation(MapLocation mapLocation) async {
_appDatabase.mapLocationDao.insertMapLocation(
mapLocation
);
}
#override
Future<void> insertReplaceMapLocations(List<MapLocation> mapLocations) async {
_appDatabase.mapLocationDao.insertMapLocations(
mapLocations
);
}
}
How should I return AppDatabase from init() ?
Maybe you should change the type of the init() function to AppDatabase instead of Future<AppDatabase>? For me it seems that the code is right one and should return AppDatabase.
The init method returns a future, since you wait for it ( and it is a recommended way)
if you would like to return the AppDatabase only, rewrite it as follows::
static AppDatabase init() {
return $FloorAppDatabase.databaseBuilder(m_work_config.mWorkFloorDb).build().then((AppDatabase db) => db);}
Doing this will have some implications though, this wont be awaited meaning that any call depending on this would return late..
I'd recommend using an await clause to the callee,
for example
static Future<AppDatabase> init() async {
return await $FloorAppDatabase.databaseBuilder(m_work_config.mWorkFloorDb).build();
}
and then calling it as::
final AppDatabase db = await (...........);
or:::
YourClass.init().then((AppDatabase db) { /* anything here*/});
I am new in this state management world. So I was trying to follow this tutorial (How to Save Products in a Wishlist using the BloC Pattern - EP10 - The eCommerce Series), but mapEventToState is deprecated so I am not sure what to do.
Here is my state:
part of 'wishlist_bloc.dart';
abstract class WishlistState extends Equatable {
const WishlistState();
#override
List<Object> get props => [];
}
class WishlistLoading extends WishlistState {}
class WishlistLoaded extends WishlistState {
final WishlistModel wishlist;
const WishlistLoaded({this.wishlist = const WishlistModel()});
#override
List<Object> get props => [wishlist];
}
class WishlistError extends WishlistState {}
Here is my event:
part of 'wishlist_bloc.dart';
abstract class WishlistEvent extends Equatable {
const WishlistEvent();
#override
List<Object> get props => [];
}
class StartWishlist extends WishlistEvent {}
class AddWishlistProduct extends WishlistEvent {
final ProductModel product;
const AddWishlistProduct(this.product);
#override
List<Object> get props => [product];
}
class RemoveWishlistProduct extends WishlistEvent {
final ProductModel product;
const RemoveWishlistProduct(this.product);
#override
List<Object> get props => [product];
}
Here is my bloc:
import '../models/product_model.dart';
import '../models/wishlist_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
part 'wishlist_event.dart';
part 'wishlist_state.dart';
class WishlistBloc extends Bloc<WishlistEvent, WishlistState> {
WishlistBloc() : super(WishlistLoading()) {
on<StartWishlist>(_mapStartWishlistToState);
on<AddWishlistProduct>(_mapAddWishlistToState);
on<RemoveWishlistProduct>(_mapRemoveWishlistToState);
}
void _mapStartWishlistToState(event, emit) async {
emit(WishlistLoading());
try {
await Future.delayed(Duration(seconds: 1));
emit(WishlistLoaded());
} catch (_) {}
}
// Error ...
void _mapAddWishlistToState(event, emit) async {
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from(state.wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
void _mapRemoveWishlistToState(event, emit) async {}
}
But I get this error instead: "The getter 'wishlist' isn't defined for the type 'WishlistState'. Try importing the library that defines 'wishlist', correcting the name to the name of an existing getter, or defining a getter or field name 'wishlist'".
How to access 'wishlist' in the new version of flutter_bloc? Thank you.
Try to define arguments types while difining the functions. So your updated code would look like this:
import '../models/product_model.dart';
import '../models/wishlist_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
part 'wishlist_event.dart';
part 'wishlist_state.dart';
class WishlistBloc extends Bloc<WishlistEvent, WishlistState> {
WishlistBloc() : super(WishlistLoading()) {
on<StartWishlist>(_mapStartWishlistToState);
on<AddWishlistProduct>(_mapAddWishlistToState);
on<RemoveWishlistProduct>(_mapRemoveWishlistToState);
}
void _mapStartWishlistToState(
// Added argument types below
StartWishlist event, Emitter<WishlistState> emit) async {
emit(WishlistLoading());
try {
await Future.delayed(Duration(seconds: 1));
emit(WishlistLoaded());
} catch (_) {}
}
void _mapAddWishlistToState(
// Added argument types below
AddWishlistProduct event, Emitter<WishlistState> emit) async {
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from(state.wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
void _mapRemoveWishlistToState(
// Added argument types below
RemoveWishlistProduct event, Emitter<WishlistState> emit) async {}
}
Type promotion is impossible for properties, since they can potentially return different values each time they are called. As such, it is impossible for the compiler to know that the state getter will return a WishlistLoaded instance, even after knowing that the same getter returned a WishlistLoaded four lines earlier.
One way around this is to assign the state to a local variable, which is eligible for type promotion.
void _mapAddWishlistToState(AddWishlistProduct event, Emitter<WishlistState> emit) async {
final state = this.state; // local variable
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from(state.wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
The linked video used a parameter, which is also eligible for type promotion. If it had used the state getter directly in _mapAddWishlistProductToState, it would have run into the same error.
You just need to cast your state as following
void _mapAddWishlistToState(event, emit) async {
if (state is WishlistLoaded) {
try {
emit(WishlistLoaded(
wishlist: WishlistModel(
products: List.from((state as WishlistLoaded).wishlist.products)
..add(event.product))));
} catch (_) {}
}
}
I am following along with this tutorial and up to this point have been understanding and bringing the code up to the most recent standard (null safety, changes in plugins, etc); however I am now stuck with how to replace transformEvents with on<Event> with an EventTransformer.
I am presented with the following three errors for the code (at the end).
'LoginBloc.transformEvents' ('Stream<LoginState> Function(Stream<LoginEvent>, Stream<LoginState> Function(LoginEvent))') isn't a valid override of 'Bloc.transformEvents' ('Stream<Transition<LoginEvent, LoginState>> Function(Stream<LoginEvent>, Stream<Transition<LoginEvent, LoginState>> Function(LoginEvent))').
A value of type 'Stream<Transition<LoginEvent, LoginState>>' can't be returned from the method 'transformEvents' because it has a return type of 'Stream<LoginState>'.
The argument type 'Stream<LoginState> Function(LoginEvent)' can't be assigned to the parameter type 'Stream<Transition<LoginEvent, LoginState>> Function(LoginEvent)'.
I have done the normal thing of checking the documentation and examples for the bloc Dart Package, but unfortunately it is not terribly clear as to the way in which it should be implemented to bring it to the current standard (or at least not clear enough for myself to understand).
The below is a minimum reproducible example of code that is causing me grief.
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:meta/meta.dart';
import 'package:pawzed/repositories/userRepository.dart';
import 'package:rxdart/rxdart.dart';
part 'login_event.dart';
part 'login_state.dart';
class LoginBloc extends Bloc<LoginEvent, LoginState> {
UserRepository _userRepository;
LoginBloc({required UserRepository userRepository})
: //assert(userRepository != null), //will always be null
_userRepository = userRepository,
super(LoginState.empty()); //added LoginState.empty()
LoginState get initialState => LoginState.empty();
#override
Stream<LoginState> transformEvents(
Stream<LoginEvent> events,
Stream<LoginState> Function(LoginEvent event) next,
) {
final nonDebounceStream = events.where((event) {
return (event is! EmailChanged || event is! PasswordChanged);
});
final debounceStream = events.where((event) {
return (event is EmailChanged || event is PasswordChanged);
}).debounceTime(Duration(milliseconds: 300));
return super.transformEvents(
nonDebounceStream.mergeWith([debounceStream]),
next,
);
}
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
}
});
}
}