How do I use a StreamProvider from a StateNotifierProvider? - flutter

I am try to use a StreamProvider from a StateNotifierProvider.
Here is my StreamProvider, which works fine so far.
final productListStreamProvider = StreamProvider.autoDispose<List<ProductModel>>((ref) {
CollectionReference ref = FirebaseFirestore.instance.collection('products');
return ref.snapshots().map((snapshot) {
final list = snapshot.docs
.map((document) => ProductModel.fromSnapshot(document))
.toList();
return list;
});
});
Now I am trying to populate my shopping cart to have all the products in it from scratch.
final cartRiverpodProvider = StateNotifierProvider((ref) =>
new CartRiverpod(ref.watch(productListStreamProvider));
This is my CartRiverPod StateNotifier
class CartRiverpod extends StateNotifier<List<CartItemModel>> {
CartRiverpod([List<CartItemModel> products]) : super(products ?? []);
void add(ProductModel product) {
state = [...state, new CartItemModel(product:product)];
print ("added");
}
void remove(String id) {
state = state.where((product) => product.id != id).toList();
}
}

The simplest way to accomplish this is to accept a Reader as a parameter to your StateNotifier.
For example:
class CartRiverpod extends StateNotifier<List<CartItemModel>> {
CartRiverpod(this._read, [List<CartItemModel> products]) : super(products ?? []) {
// use _read anywhere in your StateNotifier to access any providers.
// e.g. _read(productListStreamProvider);
}
final Reader _read;
void add(ProductModel product) {
state = [...state, new CartItemModel(product: product)];
print("added");
}
void remove(String id) {
state = state.where((product) => product.id != id).toList();
}
}
final cartRiverpodProvider = StateNotifierProvider<CartRiverpod>((ref) => CartRiverpod(ref.read, []));

Related

NotifyListner is not working for List<String> and working for other object

In SearchData class any change in result update the UI but any change in history, is not updating the UI, even tho the implemenation of both is same. I'm calling notify for both of em.
Below is the class code
class SearchData<T> {
List<R> copyList<R>(List<R>? list) => List.from(list ?? []);
SearchData(this.notify, this.historyKey) {
_loadHistory();
}
final void Function([VoidCallback? action]) notify;
final String historyKey;
int page = 1;
List<T>? _result;
List<T>? get result => _result;
// search history
List<String> _history = [];
List<String> get history => _history;
void _loadHistory() async {
final data = await DataBox(historyKey).readStringList();
if (kDebugMode) {
print('hello loaded history = $data');
}
if (data != null) notify(() => _history = copyList(data));
}
void updateSearchResult(String query, List<T>? newResult, bool isLoadMore) {
if (newResult == null) return;
notify(() {
if (isLoadMore) {
_result = copyList(result)..addAll(newResult);
} else {
_result = newResult;
}
});
// save local history of search
if (!_history.contains(query)) {
_history.add(query);
DataBox(historyKey).writeStringList(history);
}
}
}
I tried the workoaroud of creating new instance of list, so notify listner could update the UI.

How to compose async action and StateNotifierProvider?

I have some stream source (from FlutterReactiveBle library) and reflect it to state managed by StateNotifier.
But I can't sure whether it is right way from the following source. I'm especially afraid of _setState invalidates connectionProvider. And it looks like a bit complicated.
How can I improve this?
It may not work because I wrote it just for illustration.
#freezed
class DeviceConnections with _$DeviceConnections {
const DeviceConnections._();
const factory DeviceConnections({
Map<String, StreamSubscription<void>> connectings,
MapEntry<String, StreamSubscription<void>>? connected,
}) = _DeviceConnections;
}
class SimpleStateNotifier<T> extends StateNotifier<T> {
SimpleStateNotifier(super.state);
void update(T newState) {
state = newState;
}
}
StateNotifierProvider<SimpleStateNotifier<T>, T> simpleStateNotifierProvider<T>(
T initialState,
) {
return StateNotifierProvider<SimpleStateNotifier<T>, T>((ref) {
return SimpleStateNotifier(initialState);
});
}
class DeviceConnector {
DeviceConnector({
required FlutterReactiveBle ble,
required DeviceConnections state,
required Function(DeviceConnections) setState,
required Iterable<String> deviceIds,
}) : _ble = ble,
_state = state,
_setState = setState,
_deviceIds = deviceIds;
final FlutterReactiveBle _ble;
final DeviceConnections _state;
final Function(DeviceConnections) _setState;
final Iterable<String> _deviceIds;
void connect() {
final subscriptions = <String, StreamSubscription<void>>{};
for (final id in _deviceIds) {
subscriptions[id] = _connectInterval(id).listen((event) {});
}
_setState(_state.copyWith(connectings: subscriptions));
}
void disconnect() {
for (final subscription in _state.connectings.values) {
subscription.cancel();
}
_state.connected?.value.cancel();
_setState(DeviceConnections());
}
Stream<void> _connectInterval(String id) async* {
while (true) {
final connection = _ble.connectToDevice(
id: id,
connectionTimeout: Duration(seconds: 10),
);
await for (final update in connection) {
switch (update.connectionState) {
case DeviceConnectionState.connected:
final subscription = _state.connectings[id];
if (subscription != null) {
final others =
_state.connectings.entries.where((x) => x.key != id).toList();
for (final connection in others) {
connection.value.cancel();
}
_setState(
DeviceConnections(connected: MapEntry(id, subscription)),
);
}
break;
default:
break;
}
}
}
}
}
final connectionStateProvider = simpleStateNotifierProvider(
DeviceConnections(),
);
final bleProvider = Provider((_) => FlutterReactiveBle());
class AnotherState extends StateNotifier<List<String>> {
AnotherState(super.state);
}
final anotherStateNotifierProvider = StateNotifierProvider<AnotherState, List<String>>((ref) {
return AnotherState([]);
});
final connectionProvider = Provider((ref) {
final ble = ref.watch(bleProvider);
final connectorState = ref.watch(connectionStateProvider);
final connectorNotifier = ref.watch(connectionStateProvider.notifier);
final deviceIds = ref.watch(anotherStateNotifierProvider);
final connector = DeviceConnector(
ble: ble,
deviceIds: deviceIds,
state: connectorState,
setState: connectorNotifier.update,
);
ref.onDispose(connector.disconnect);
return connector;
});

How do I listen to two lists within a class in Flutter riverpod?

class QuestionPaperController extends StateNotifier<List<String>> {
QuestionPaperController() : super([]);
Future<void> getAllPapers(WidgetRef ref) async {
List<String> imgName = ["biology", "chemistry", "maths", "physics"];
try {
for (var img in imgName) {
final imgUrl = await ref.read(firebaseStorageProvider).getImage(img);
state = [...state, imgUrl!];
}
} catch (e) {
print(e);
}
}
}
final questionPaperControllerProvider =
StateNotifierProvider<QuestionPaperController, List<String>>((ref) {
return QuestionPaperController();
});
I want to add another list that its name will stackoverflow for this class and watch it but statenotifier listening another list what can I do?
You need to create another instance of the class
class StackoverflowController extends StateNotifier<List<String>> {
/// ...
}
final stackoverflowControllerProvider =
StateNotifierProvider<StackoverflowController, List<String>>((ref) {
return StackoverflowController();
});
and create provider that watch the other two
final otherProvider = Provider<...>((ref) {
ref.watch(stackoverflowControllerProvider);
ref.watch(questionPaperControllerProvider );
return ...;
});
bonus: you can pass ref in class-controller:
final fizzControllerPr = Provider.autoDispose((ref) => FizzController(ref));
// or use tear-off
final fizzControllerPr1 = Provider.autoDispose(FizzController.new);
/// Class represent controller.
class FizzController {
FizzController(this._ref);
final Ref _ref;
Future<void> getAllPapers() async {
//...
final imgUrl = await _ref.read(firebaseStorageProvider).getImage(img);
//...
}
}

Flutter Riverpod : How to Implement FutureProvider?

I using Flutter Riverpod package to handling http request. I have simple Http get request to show all user from server, and i using manage it using FutureProvider from Flutter Riverpod package.
API
class UserGoogleApi {
Future<List<UserGoogleModel>> getAllUser() async {
final result = await reusableRequestServer.requestServer(() async {
final response =
await http.get('${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getAllUser');
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
}
User Provider
class UserProvider extends StateNotifier<UserGoogleModel> {
UserProvider([UserGoogleModel state]) : super(UserGoogleModel());
Future<UserGoogleModel> searchUserByIdOrEmail({
String idUser,
String emailuser,
String idOrEmail = 'email_user',
}) async {
final result = await _userGoogleApi.getUserByIdOrEmail(
idUser: idUser,
emailUser: emailuser,
idOrEmail: idOrEmail,
);
UserGoogleModel temp;
for (var item in result) {
temp = item;
}
state = UserGoogleModel(
idUser: temp.idUser,
createdDate: temp.createdDate,
emailUser: temp.emailUser,
imageUser: temp.emailUser,
nameUser: temp.nameUser,
tokenFcm: temp.tokenFcm,
listUser: state.listUser,
);
return temp;
}
Future<List<UserGoogleModel>> showAllUser() async {
final result = await _userGoogleApi.getAllUser();
state.listUser = result;
return result;
}
}
final userProvider = StateNotifierProvider((ref) => UserProvider());
final showAllUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.showAllUser();
return result;
});
After that setup, i simply can call showAllUser like this :
Consumer((ctx, read) {
final provider = read(showAllUser);
return provider.when(
data: (value) {
return ListView.builder(
itemCount: value.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
final result = value[index];
return Text(result.nameUser);
},
);
},
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error $error'),
);
}),
it's no problem if http request don't have required parameter, but i got problem if my http request required parameter. I don't know how to handle this.
Let's say , i have another http get to show specific user from id user or email user. Then API look like :
API
Future<List<UserGoogleModel>> getUserByIdOrEmail({
#required String idUser,
#required String emailUser,
#required String idOrEmail,
}) async {
final result = await reusableRequestServer.requestServer(() async {
final baseUrl =
'${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getUserByIdOrEmail';
final chooseURL = idOrEmail == 'id_user'
? '$baseUrl?id_or_email=$idOrEmail&id_user=$idUser'
: '$baseUrl?id_or_email=$idOrEmail&email_user=$emailUser';
final response = await http.get(chooseURL);
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
User Provider
final showSpecificUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.searchUserByIdOrEmail(
idOrEmail: 'id_user',
idUser: usrProvider.state.idUser, // => warning on "state"
);
return result;
});
When i access idUser from userProvider using usrProvider.state.idUser , i got this warning.
The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'.
It's similiar problem with my question on this, but on that problem i already know to solved using read(userProvider.state) , but in FutureProvider i can't achieved same result using ref(userProvider).
I missed something ?
Warning: This is not a long-term solution
Assuming that your FutureProvider is being properly disposed after each use that should be a suitable workaround until the new changes to Riverpod are live. I did a quick test to see and it does work. Make sure you define a getter like this and don't override the default defined by StateNotifier.
class A extends StateNotifier<B> {
...
static final provider = StateNotifierProvider((ref) => A());
getState() => state;
...
}
final provider = FutureProvider.autoDispose((ref) async {
final a = ref.read(A.provider);
final t = a.getState();
print(t);
});
Not ideal but seems like a fine workaround. I believe the intention of state being inaccessible outside is to ensure state manipulations are handled by the StateNotifier itself, so using a getter in the meantime wouldn't be the end of the world.

adding list of stream to bloc flutter

Hello i am trying to get data from firestore map it to a stream i am using Rxdart how do i add this Stream of list of product to the stream.When i tried to do it it keeps saying.The argument type Stream> can't be assigned to parameter type List.I am new to flutter please help.
Product Service;
class ProductService {
Firestore _db = Firestore.instance;
var random = Random();
Stream<List<Product>> fetchProducts() {
return _db.collection('products').snapshots().map(
(snapshot) => snapshot.documents
.map((document) => Product.fromFirestore(document.data))
.toList(),
);
}
}
bloc
class ProductBloc {
final _products = BehaviorSubject<List<Product>>();
final ProductService _db = ProductService();
//getters
Stream<List<Product>> get products => _products.stream;
Function(List<Product>) get changeProducts => _products.sink.add;
loadData() async {
try {
var products = _db.fetchProducts();
_products.sink.add(products);
} catch (err) {
print(err);
}
}
dispose() {
_products.close();
}
}