Handling api response with bloc pattern with rxdart - flutter

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.

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 call a riverpod provider outside of widget tree/widget class?

I am new to using RiverPod. previously I used Provider for state management.
in case of provider I could use a provider outside widget tree to get value using syntax
Provider.of<MyModel>(context,listen:true).someFunction();
how do I do the same in RiverPod? for now I am using Consumer Builder and Consumer Widget. I was wondering if there's a way to call a riverpod provider without using Consumer.
You can easily call riverpord provider without using Consumer. Please check out my below extension which is very helpful to call the provider based on context where it will work for the read function without any consumer or consumer widget.
extension Context on BuildContext {
// Custom call a provider for reading method only
// It will be helpful for us for calling the read function
// without Consumer,ConsumerWidget or ConsumerStatefulWidget
// Incase if you face any issue using this then please wrap your widget
// with consumer and then call your provider
T read<T>(ProviderBase<T> provider) {
return ProviderScope.containerOf(this, listen: false).read(provider);
}
}
After then under the build method, you must call context.read(yourProviderName)
let's say you have a separate class that is responsible for ui logic:
class UiController {
UiController(this._ref);
final Ref _ref;
Reader get _reader => _ref.read;
static final pr = Provider<UiController>((ref) => UiController(ref));
void someFunction() {
_reader(otherProvider).getValue();
}
}
from a widget or other functions where ref is accessed, you can call:
ref.read(UiController.pr).someFunction();

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!

Best practice to use mapEventToState with incoming streams?

Is there any elegant way to map incoming streams from a private api directly inside mapEventToState() without having to create redundant private events in the bloc?
I came with this solution. It's ok with one single stream, but with multiple streams it starts to get a mess. Thanks in advance.
// (don't mind the imports, this is the bloc file)
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
final MyPrivateApi api = MyPrivateApi.instance; // singleton
ExampleBloc() {
// api has a stream of booleans
api.myStream.listen((b) {
// if it's true fire this event
if (b) this.add(_MyPrivateEvent());
}
#override
ExampleState get initialState => InitialExampleState();
#override
Stream<ExampleState> mapEventToState(
ExampleEvent event,
) async* {
if (event is _MyPrivateEvent) {
yield SomeState;
}
}
// private Event
class _MyPrivateEvent extends ExampleEvent {
}
As I can see, you can subscribe on event updates in your screen, and push event from screen to Bloc if need some calculations. Code will be more clean.
Your way seems to be the only way works and seems to be used - see this bloc issue: https://github.com/felangel/bloc/issues/112 and this example project: https://github.com/algirdasmac/streams_and_blocs
Just make sure to dispose the subscription that gets returned by api.myStream.listen.
Previous answer
The following DOES NOT work for infinite streams because the generator function will await until the stream finishes. This can only be used for stream the complete fast, like maybe an upload/download progress.
See accepted answers here Dart yield stream events from another stream listener and here Dart/Flutter - "yield" inside a callback function
ExampleBloc() {
_MyInitEvent();
}
#override
Stream<ExampleState> mapEventToState(
ExampleEvent event,
) async* {
if (event is _MyInitEvent) {
await for (bool b in api.myStream) {
if (b) yield SomeState;
}
}
}
Build another block that encapsulate your stream of bytes.
You can make two events (ByteRead and ByteConsume) and two states (ByteWaiting and ByteAvailable).
Byteread and ByteAvailable should have a _byte field for storing data. Your bytebloc has a subscriber listening the stream and every time it reads a byte it fires a ByteRead event.
You should also add to the bloc a consume() method that gives the last byte readed and fires the ByteConsume event.
The two states and events are mutual:
you start in bytewaiting and every time you have a ByteRead you change to ByteAvailable and
every time you have a ByteConsume you change back to ByteWaiting.

Initial value with StreamController without RxDart

I'm using StreamControllers with Flutter. I have a model with some default values. From the widgets where I'm listening to the stream I want to supply some of those default values. I can see I can set an initial value on the StreamBuilder, but I want to use data from the model inside the bloc as initial data. So as soon as someone is using the snapshot data they get the default values. I've seen RxDart has a seed value, just wondering if this is possible without replacing with RxDart?
What you are looking for is StreamController#add method,
Sends a data event.
Listeners receive this event in a later microtask.
Note that a synchronous controller (created by passing true to the
sync parameter of the StreamController constructor) delivers events
immediately. Since this behavior violates the contract mentioned here,
synchronous controllers should only be used as described in the
documentation to ensure that the delivered events always appear as if
they were delivered in a separate microtask.
happy fluttering
The default value for a stream can be specified when the class is initialized after adding a listener for that stream.
import 'dart:async';
enum CounterEvent { increase }
class CounterBloc {
int value = 0;
final _stateCntrl = StreamController<int>();
final _eventCntrl = StreamController<CounterEvent>();
Stream<int> get state => _stateCntrl.stream;
Sink<CounterEvent> get event => _eventCntrl.sink;
CounterBloc() {
_eventCntrl.stream.listen((event) {
_handleEvent(event);
});
_stateCntrl.add(value); // <--- add default value
}
void dispose() {
_stateCntrl.close();
_eventCntrl.close();
}
_handleEvent(CounterEvent event) async {
if (event == CounterEvent.increase) {
value++;
}
_stateCntrl.add(value);
}
}