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);
}
}
Related
Problem Summary:
I'm trying to fetch a list from StateA of BlocA when I create a new bloc.
Simplified Background:
I have an overarching bloc (BlocA), that is always active in the context of this problem, and 2 screens with a corresponding bloc each (BlocB & BlocC) that gets created when routing to its associated screen and closes when routing away from its associated screen. Every time a new bloc is created it needs to fetch its data from the state of BlocA. The user might move back and forth between screens.
What I tried:
I created stream controllers in BlocA that streams relevant data to each of the blocs via a getter. At first, I tried a normal (single listner) stream which worked fine initially. However, when routing away and then back to the screen it throws an error when resubscribing to the same stream using the getter. I then instantiated the stream controller as a broadcast stream StreamController.broadcast(). The problem is then that, when subscribing to the stream, no data is passed on subscription to the stream like with a normal stream and when I try to implement an onListen callback in the broadcast constructor (to add an event to the sink) it gives me an error The instance member '_aStream' can't be accessed in an initializer. A similar error appears for state. See below:
... _aStream = StreamController.broadcast(onListen: () {return _aStream.add(state.TypeX)})
Simplified Example Code:
class BlocA extends Bloc<BlocAEvent, BlocAState> {
BlocA() : super(BlocAState()) {
on<EventA>(_onevent);
}
final StreamController<TypeX> _aStream = StreamController.broadcast();
Stream<TypeX> get aStream => _aStream.stream;
final StreamController<TypeY> _bStream = StreamController.broadcast();
Stream<TypeY> get bStream => _bStream.stream;
...
// sink.add()'s are implemented in events
}
class BlocB extends Bloc<BlocBEvent, BlocBState> {
BlocB({required this.blocA}) : super(BlocBState()) {
on<EventB>(_onEventB);
blocASubscription = blocA.aStream.listen((stream) {
if (stream != state.fieldX) {
add(EventB(fieldX: stream));
}
});
}
final BlocA blocA
late final StreamSubscription blocASubscription;
FutureOr<void> _onEventB(EventB event, Emitter<BlocBState> emit) {
emit(state.copyWith(fieldX: event.fieldX));
}
}
class BlocC extends Bloc<BlocCEvent, BlocCState> {
// similar to BlocB
}
You do not need a stream, because bloc underhood is on streams yet. You can sent everything what you want through events and states. Check the library of Angelov https://bloclibrary.dev/#/
I ended up staying with the stream controllers, as used in the example code, but created a new event for BlocA where it is triggered when the user changes between screens and sinks the appropriate state data into the stream. The event carried an index field to indicate the screen that was routed to. The event's index corresponds with the navBar index.
The event handling implementation looked like this:
FutureOr<void> _onScreenChanged(
ScreenChanged event,
Emitter<BlocAState> emit,
) async {
switch (event.index) {
case 0:
_aStream.sink.add(state.fieldX);
break;
case 1:
_bStream.sink.add(state.fieldY);
break;
default:
}
}
I have a Cubit that retrieves json data from an API. It processes the data and based on the processing, will need to change the state of multiple widgets.
Essentially, using some if statements, the state changes will need to be emitted if the data matches certain criterion.
This code sample shows the idea, but I'm not sure how to actually fulfill the need within the if statements.
import 'dart:convert';
import 'package:bloc/bloc.dart';
import 'package:dio/dio.dart';
class ProcessingCubit extends Cubit<String> {
ProcessingCubit() : super("");
void getDataFromAPI() async {
Response response;
var dio = Dio();
response = await dio.get(
'http://our.internalserver.com:8080/api/getdata.php',
queryParameters: {});
var parsedjsonresponse = json.decode(response.data.toString());
//the json returned is an array of objects. For this code example,
//we're only going through slot 0 of the array of objects
if (!parsedjsonresponse['ourdata'].isEmpty) {
print(parsedjsonresponse['ourdata']);
}
if (!parsedjsonresponse['ourdata'][0]['code'] == "001") {
//emit state for this code, so that the necessary widget
//will show something
}
if (!parsedjsonresponse['ourdata'][0]['code'] == "002") {
//emit state for this code, so that the necessary widget will
//show something (different widget than the "if" block above
}
if (!parsedjsonresponse['ourdata'][0]['alert'] == "1") {
//emit state for this alert so that the alert widget
//will show something
}
}
}
Sometimes none of the if statements will need to change state, sometimes all may need to, and sometimes only some.
You can emit states using:
emit(CubitState);
Since you declared your Cubit State to be a String it would be:
emit("apiResponseAsString");
You can emit as much states as you want. So for each of your ifs you can emit the according string.
The official documentation for the bloc library gives you great examples for cubits.
I have this ViewModel and a Riverpod provider for it:
final signInViewModelProvider = Provider.autoDispose<SignInViewModel>((ref) {
final vm = SignInViewModel();
ref.onDispose(() {
vm.cleanUp();
});
return vm;
});
class SignInViewModel extends VpViewModelNew {
FormGroup get form => _form;
String get emailKey => _emailKey;
String get passwordKey => _passwordKey;
final String _emailKey = UserSignInFieldKeys.email;
final String _passwordKey = UserSignInFieldKeys.password;
final FormGroup _form = FormGroup({
UserSignInFieldKeys.email:
FormControl<String>(validators: [Validators.required]),
UserSignInFieldKeys.password:
FormControl<String>(validators: [Validators.required])
});
void cleanUp() {
print('cleaning up');
}
void onSubmitPressed(BuildContext context) {
// _saveRegistrationLocallyUseCase.invoke(
// form.control(_self.emailKey).value as String ?? '',
// form.control(_self.passwordKey).value as String ?? '');
}
}
abstract class VpViewModelNew {
VpViewModelNew() {
if (onCreate != null) {
onCreate();
print('creating');
}
}
void onCreate() {}
}
When I navigate to the page that has the signInViewModelProvider, it prints to the console:
flutter: signInPage building
flutter: creating
flutter: cleaning up
Then popping the page from the stack with Navigator.pop() prints nothing.
Then navigating to the page again prints the same 3 lines in the same order.
I expected onDispose to be called after Navigator.pop(), and not when navigating to the page that reads the provider. Why is onDispose being called directly after creation, and not when using Navigator.pop() (when I expected the provider to be disposed of since no other views reference it)?
Edit: I access the provider with final viewModel = context.read<SignInViewModel>(signInViewModelProvider);
I don't need to listen since I don't need to rebuild the page on
change. Is consumer less performant for this?
No, the performance is meaningless, even if it's listening it's not really affecting the performance because as a Provider there is no way to notify (which is not the case with a state notifier or change notifier)
Also if you don't care to listen after the value has been read The auto dispose understand no one is watching it and it disposes, it's better to use context.read when using tap or gestures that modify something
(I realize this is late to the party but maybe it'll help somebody)
The Riverpod docs come out pretty strongly against using read for the reason you said, i.e. performance/rebuilding concerns.
Basically you should always use watch except:
If you want your custom callback function called when it updates (use listen)
If the actual reading is happening asynchronously or in response to user action (like in an onPressed): this is the only time to use read.
If you're having issues with your widgets rebuilding too often, Riverpod has some ways to deal with that that don't involve using read.
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.
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.