How to handle stream list in flutter MVVM architecture? - flutter

I have a list of values coming from my server and I want to display each of these values in a checkbox. I want a stream for each checkbox but I do not know the number of values which will come from the api. It can be any number. This is my code for view model class:
class SettingsViewModel extends BaseViewModel
with SettingsViewModelInputs, ViewModelOutputs {
final StreamController _deliverySettingCheckboxValue =
StreamController<List<String>>.broadcast();
#override
void start() {
settings();
}
#override
void dispose() {
_deliverySettingCheckboxValue.dispose();
}
#override
List<Stream<void>> get outputCheckboxValue =>
_deliverySettingCheckboxValue.stream.map((checkBoxValue) => checkBoxValue);
}
abstract class SettingsViewModelInputs {
}
abstract class SettingsViewModelOutputs {
List<Stream<void>> get outputCheckboxValue;
}
In the settings() method I make my api call and get the number of checkbox I wish to have in my screen. How do I handle the list of streams in this scenario?

Related

How do I update Flutter's Riverpod values from business logic?

When using Flutter and Riverpod, how do I update its values from my business logic?
I understand that I can get and set values from the UI side.
class XxxNotifier extends StateNotifier<String> {
XxxNotifier() : super("");
}
final xxxProvider = StateNotifierProvider<XxxNotifier, int>((ref) {
return XxxNotifier();
});
class MyApp extends HookConsumerWidget {
#override
Widget build(BuildContext context, WidgetRef ref) {
// getValue
final String value = ref.watch(xxxProvider);
// setValue
context.read(xxxProvider).state = "val";
return Container();
}
}
This method requires a context or ref.
How do I get or set these states from the business logic side?
Passing a context or ref from the UI side to the business logic side might do that, but I saw no point in separating the UI and business logic. Perhaps another method exists.
Perhaps I am mistaken about something. You can point it out to me.
You can pass ref in your XxxNotifier class:
class XxxNotifier extends StateNotifier<String> {
XxxNotifier(this._ref) : super("");
final Ref _ref;
void setNewState() {
state = 'to setting';
// use `_ref.read` to read state other provider
}
}
final xxxProvider = StateNotifierProvider<XxxNotifier, int>((ref) {
return XxxNotifier(ref);
});
// or using tear-off
final xxxProvider = StateNotifierProvider<XxxNotifier, int>(XxxNotifier.new);
You can create methods in your XxxNotifier class to modify the state of your provider.
For example, your notifier class can look like this.
class TodosNotifier extends StateNotifier <List<Todo>> {
TodosNotifier(): super([]);
void addTodo(Todo todo) {
state = [...state, todo];
}
}
You can then read the provider in a callback.
ref.read(xxxProvider.notifier).addTodo(todo);

events management flutter bloc pattern

everyone. I am new in Flutter and BLoC pattern.
I needed to implement contact page so I created event GetContacts and passed it into context.read().add() after that I called this event into initState() of contacts screen.
Here my event:
abstract class ContactEvent extends Equatable {
const ContactEvent([List props = const []]) : super();
}
class GetContacts extends ContactEvent {
const GetContacts() : super();
#override
List<Object> get props => [];
}
Here is my bloc:
class ContactsBloc extends Bloc<ContactEvent, ContactsState> {
final ContactsRepo contactsRepo;
ContactsBloc({required this.contactsRepo}) : super(ContactInitial());
#override
Stream<ContactsState> mapEventToState(ContactEvent event,) async* {
yield ContactsLoading();
//
// if (event is UpdatePhoto) {
// yield PhotoLoading();
//
// print("LOADING STARTED");
//
// final photo = await contactsRepo.updatePhoto(event.identifier, event.photo);
// print("LOADING FINISHED");
//
// yield PhotoLoaded(photo: photo);
// }
if (event is GetContacts) {
print("get contacts photoBloc");
try {
final contacts = await contactsRepo.getContacts();
yield ContactsLoaded(contacts);
} on AccessException {
yield ContactsError();
}
}
}
}
That works right and contacts page renders contacts as it is supposed.
[contacts screen][1]
[1]: https://i.stack.imgur.com/Gx3JA.png
But then I decided to implement new feature: when user clicks on any contact he is offered to change its photo.
If I understand BLoC pattern correctly then if I want to change my state I need to create new event. Then I created new action UpdatePhoto and passed it into the same Bloc as it shown at 2nd part of code (in comments). Exactly there I encounter a misunderstanding of architecture expansion. This action is not supposed to return ContactsLoaded state so when I tried to catch this into my another bloc builder it broke my previous bloc builder that caught GetContact event.
ContactState:
abstract class ContactsState extends Equatable {
const ContactsState([List props = const []]) : super();
}
// class PhotoLoading extends PhotoState {
// #override
// List<Object?> get props => [];
// }
//
// class PhotoLoaded extends PhotoState {
// final Uint8List photo;
// const PhotoLoaded({required this.photo});
// #override
// List<Object?> get props => [photo];
// }
class ContactInitial extends ContactsState {
#override
List<Object> get props => [];
}
class ContactsLoading extends ContactsState {
#override
List<Object> get props => [];
}
class ContactsLoaded extends ContactsState {
final List<MyContact> contacts;
ContactsLoaded(this.contacts) : super([contacts]);
#override
List<Object> get props => [contacts];
}
class ContactsError extends ContactsState {
#override
List<Object?> get props => [];
}
Question: If I want to create new event (for example UpdatePhoto) which is not supposed to return the state that I caught before at the same bloc then I need to create new bloc for that purpose and cover my screen by multiProvider?
You should also post your ContactState code.
However you do not necessarely need a new Bloc. It all depends on what you are trying to achieve.
I suppose than when you yield PhotoLoading() you want to show a loader.
But when you update the photos, if I understand what you are trying to achieve you should yield an updated list of contacts using again yield ContactsLoaded(contacts) or add(GetContacts())instead of yield PhotoLoaded(photo: photo).
If you want to show a confirmation message, you can keep your PhotoLoaded state, but you need to build your UI taking into account the different state the bloc may emit.
Remember in BloC architecture event can yield to multiple states in successions and the UI decide if and how to react to each state.
I guess use optional parameter buildWhen in BlocBuilder is the best way to avoid creating new bloc for each event.

flutter_bloc - hook into onClose, onCreate lifecycle events for specific cubit

I want to hook into the lifecycle events of my cubits.
I notice the blocObserver has onClose and onCreate, however that is for observing all cubit's lifecycle events. How do I hook into these events just for a specific cubit? For example the equivalent of overriding onClose inside a cubit.
My implementation of ChessMax's answer:
class VpCubit<T> extends Cubit<T> {
VpCubit(T initial) : super(initial);
void onClose() => print('');
#override
Future<void> close() async {
if (onClose != null) {
onClose();
}
return super.close();
}
}
class LoggedOutNickNameCubit extends VpCubit<int> {
LoggedOutNickNameCubit()
: super(0);
#override
void onClose() {
print('closing');
}
void onCreate() {
print('creating');
}
}
One possible solution is to filter out events in the observing hook. Every event of BlocObserver has a reference to a cubit/bloc instance that sends the event. So you can compare it with the reference with your specific cubit/bloc instance. And if references are equal you can handle it somehow. Something like this:
class MyObserver extends BlocObserver {
final Cubit<Object> _cubit;
MyObserver(this._cubit) : assert(_cubit != null);
#override
void onClose(Cubit cubit) {
if (cubit == _cubit) {
// do whatever you need
}
super.onClose(cubit);
}
}
Another way is to create your own cubit/bloc subclass. Override the methods you want to listen to. And use your own BlocObserver like class to notify this specific cubit/bloc listeners. Something like this:
class MyObserver {
final void Function() onClose;
MyObserver(this.onClose);
}
class MyBloc extends Bloc<MyEvent, MyState> {
final MyObserver _observer;
MyBloc(this._observer) : super(MyInitial());
#override
Future<void> close() async {
if (_observer != null && _observer.onClose != null) {
_observer.onClose();
}
return super.close();
}
}
Also, I think, it's possible to write a generic cubit/bloc wrapper base class like above. And then use it as a base class for all your cubits/blocs classes instead.

How can I access an instance of an object in different classes / pages without a widget tree context?

I am trying to access an instance of an RtcEngine object for AgoraIO from another class/page that doesn't have a widget tree, and therefore no context to refer to with Provider.
First I'm calling initPlatformState() from this class in order to initialize the RtcEngine engine:
class Game extends StatefulWidget {
#override
_GameState createState() => _GameState();
}
class _GameState extends State<Game> implements GameListener {
#override
void initState() {
super.initState();
Agora().initPlatformState(widget.playerId);
}
}
initPlatformState initializes the RtcEngine by creating an instance called engine that I need to use later on to call other methods. This class also contains the method I want to call later using the same instance to adjustVolume...
class Agora {
RtcEngine engine;
// Initialize the agora app
Future<void> initPlatformState(int playerId) async {
RtcEngine engine = await RtcEngine.create(APP_ID);
}
void adjustVolume(int uid, int volume) {
engine.adjustUserPlaybackSignalVolume(uid, volume);
}
}
This is the class that I want to call adjustVolume from. I was considering using Provider to pass the instance to this class but it extends another class and it doesn't have a widget tree with context so I'm not sure how thats possible or if there is a better way.
class Remote extends Component {
final int id;
Remote(this.id);
#override
void update() {
//this is where I'm trying to access the engine instance that was created to call adjustUserPlaybackSignalVolume method
}
}
Any suggestions on how to reuse that instance of "engine" given my situation would be greatly appreciated!

How to clear data from Streams of blocpattern?

My this function isn't clearing the cart.
clearCart(){
_listController.close();
}
Am I supposed to call some other property or implement some other approach in clear cart function?
Here is my CartListBloc code:
class CartListBloc extends BlocBase {
CartListBloc();
var _listController = BehaviorSubject<List<FoodItem>>.seeded([]);
//provider class
CartProvider provider = CartProvider();
//output
Stream<List<FoodItem>> get listStream => _listController.stream;
//input
Sink<List<FoodItem>> get listSink => _listController.sink;
addToList(FoodItem foodItem) {
listSink.add(provider.addToList(foodItem));
}
removeFromList(FoodItem foodItem) {
listSink.add(provider.removeFromList(foodItem));
}
clearCart(){
// What should I put here to clear the bloc of Streams from cart
}
//dispose will be called automatically by closing its streams
#override
void dispose() {
_listController.close();
super.dispose();
}
}
The Stream class has a drain method, which removes all data from a Stream. However, you seem to be trying to clear a BehaviorSubject so you can't use drain (It doesn't actually clear the subject). Instead, you should probably simply add an empty List or null (in which case you need to deal with this null in your UI) to _listController, which will give you a new event with no items.
Edit:
Example:
_listController.add([]); //Now your listeners will receive
//new event with empty list of items