Pass parameter in BLoC pattern Flutter(Stream Controller) - flutter

I am new to Flutter and not sure if this is the right method to follow. I am following YouTube tutorial to create a movie app using TMDB API and trying to pass 'genreId' from category.dart(comment below) to MovieBloc.dart. Is it possible to send the 'genreId'? If yes, how can I pass it? If not, what is the best way to do it?
Thank you
category.dart
final movieBloc = MovieBloc();
setState(() {
selectedGenre = genre.id;
movieBloc.eventSink.add(MovieControlAction.byGenre); // send genreId through here??
});
movie_bloc.dart
import 'dart:async';
import 'package:flutter_api/Model/movie.dart';
import 'package:flutter_api/Service/api_service.dart';
enum MovieControlAction { fetch, delete, byGenre, }
class MovieBloc {
final service = ApiService();
final _stateStreamController = StreamController<List<Movie>>();
StreamSink<List<Movie>> get _movieSink => _stateStreamController.sink;
Stream<List<Movie>> get movieStream => _stateStreamController.stream;
final _eventStreamController = StreamController<MovieControlAction>();
StreamSink<MovieControlAction> get eventSink =>
_eventStreamController.sink; //input
Stream<MovieControlAction> get _eventStream =>
_eventStreamController.stream; //output
MovieBloc() {
_eventStream.listen((event) async {
if (event == MovieControlAction.fetch) {
try {
var movies = await service.getNowPlayingMovie();
if (movies != null) {
_movieSink.add(movies);
} else {
_movieSink.addError('Null');
}
} on Exception catch (e) {
print(e);
_movieSink.addError('Something went wrong');
}
} else if (event == MovieControlAction.byGenre) {
var moviesByGenre = await service.getMovieByGenre(genreId);
if (moviesByGenre != null) {
_movieSink.add(moviesByGenre);
} else {
_movieSink.addError('Null');
}
}
});
}
void dispose() {
_stateStreamController.close();
_eventStreamController.close();
}
}

You can pass it to the MovieBloc like follows:
final movieBloc = MovieBloc(genreId: genre.id);
And then create a constructor within the MovieBloc class:
class MovieBloc {
final String genreId; // String, int, whatever type it is
MovieBloc({
required this.genreId,
});
}
If you want to use a real Bloc, you should extend the MovieBloc like this:
class MovieBloc extends Bloc<MovieEvent, MovieState> {}
Currently it's not looking like you are using the actual Bloc pattern. Please check the official Bloc documentation. I promise - it's super helpful.

Related

Flutter Bloc/Cubit Error Handling - what is the best architectural approach?

I'm a beginner developer and I have problem with implementation of BloC framework. Let's assume that I have this code (Model, NetworkService, Repository, Cubit, State, Widget):
class NetworkService {
Future getData(Uri uri) async {
try {
http.Response httpsResponse = await http.get(
uri,
headers: {
// some headers //
},
);
if (httpsResponse.statusCode == 200) {
return httpsResponse.body;
} else {
throw 'Request failed with status: ${httpsResponse.statusCode}';
}
} catch (e) {
// What I shloud return here?
return e.toString();
}
}
Future<List<dynamic>> fetchData() async {
final uri = Uri.parse('some url');
var data = await getData(uri);
return = jsonDecode(data) as List;
}
}
class Repository {
final NetworkService networkService = NetworkService();
Future<List<SomeObject>> fetchDataList() async {
final dataRaw =
await networkService.fetchDataList();
return dataRaw.map((e) => SomeObject.fromJson(e)).toList();
}
}
class SomeCubit extends Cubit<CubitState> {
final Repository repository;
SomeCubit(this.repository) : super(LoadingState()) {
fetchDataList();
}
void fetchDataList() {
try {
repository
.fetchDataList()
.then((dataList) => emit(LoadedState(dataList)));
} catch (e) {
// What I shloud return here?
emit(ErrorState(e.toString()));
}
}
}
How to make this code "bullet proof" because I don't know how to "pass" error from NetworkService to Cubit? It works fine till I have dynamic responses in functions but in Repository class I want to return List of specific objects and when function fail I will return null. If I write try/catch I have to provide return statement in catch block - and I can't return List. I want to return some kind of Error...
I suggest that you use the excellent class named Either from the dartz package. It will allow you to return X if things went bad, and return Y if all is well, as such: Future<Either<X, Y>>
Then you can check on your variable (e.g. result) as follows: result.isLeft() for error, or do result.fold( ... ) to easily handle the return type (error or success).
In your particular case you could do as follows when returning from the repository to the cubit:
Future<Either<RepositoryError, List<SomeObject>>> fetchDataList() async { ... }
Where RepositoryError could be a class containing information about the type of error.
So in the cubit you do:
final result = await repository.fetchDataList();
emit(
result.fold(
(error) => ErrorState(error),
(dataList) => LoadedState(dataList)
)
);
Then you continue with this pattern all the way to NetworkService getData(). Either with the same common "error class" in the Repository and the NetworkService, or separate ones in the different layers and you "translate" between different "error classes". Perhaps it makes sense to have a NetworkServiceError that is returned there..
In your NetworkService you could do as follows:
Future<Either<NetworkServiceError, String>> getData(Uri uri) async { ... }
Future<Either<NetworkServiceError, List<dynamic>>> fetchData() async { ... }
This will give you great flexibility and passing of information from the service, to the repository and to the cubit.
You can let exceptions propagate through Futures from NetworkService up to the cubit, by removing the try/catch from getData.

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);
//...
}
}

The static method can't be acessed through an instance. Try using the class 'services' to acess the method

Hi can anyone help me with this problem I'm facing when calling API's in flutter, this is the code for fetching the data
class _InvestPageState extends State<InvestPage> {
late Future<Markets> _Markets;
#override
void initState() {
_Markets = Services().getMarkets(); //error here
super.initState();
}
This is the code in my API manager file
import 'package:gem_portal_new/Login/newsinfo.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class Services {
static const String url = 'https://ctrade.co.zw/mobileapi/MarketWatch';
static Future<List<Markets>> getMarkets() async {
try {
final response = await http.get(Uri.parse(url));
if (200 == response.statusCode) {
final List<Markets> markets = marketsFromJson(response.body);
return markets;
} else {
return <Markets>[];
}
} catch (e) {
return <Markets>[];
}
}
}
You are trying to access a static method using a object instance,
Change this
_Markets = Services().getMarkets();
to
_Markets = Services.getMarkets();
Try this
class _InvestPageState extends State<InvestPage> {
late Future<Markets> _Markets;
#override
void initState() {
Services().getMarkets().then((value) {
_Markets = value;
});
super.initState();
}
}
You are used future return type, so you cannot be access through instance.

How can I write "Event1 'or' Event2" inside on<Event> method from flutter_bloc?

That's my code for PostsBloc:
class PostsBloc extends Bloc<PostsEvent, PostsState> {
final _dataService = DataService();
// Constructor
PostsBloc() : super(LoadingPostsState()) {
on<LoadPostsEvent>((event, emit) async {
emit(LoadingPostsState());
try {
final posts = await _dataService.getPosts();
emit(LoadedPostsState(posts: posts));
} catch (e) {
emit(FailedToLoadPostsState(error: e));
}
});
}
}
So, I want to use the same method with new event, just without emitting LoadingPostsState() like this:
PostsBloc() : super(LoadingPostsState()) {
on<LoadPostsEvent || PullToRefreshEvent>((event, emit) async {
if(event == LoadPostsEvent){
emit(LoadingPostsState());
}
try {
final posts = await _dataService.getPosts();
emit(LoadedPostsState(posts: posts));
} catch (e) {
emit(FailedToLoadPostsState(error: e));
}
});
}
What you want is the is operator:
if (event is LoadPostsEvent)
However you run into another problem:
on<LoadPostsEvent || PullToRefreshEvent>
this is not a thing. I believe you have two options:
Either make a new event X and have LoadPostsEvent and PullToRefreshEvent extend it, like this:
class LoadEvent extends PostsEvent { ... }
class LoadPostsEvent extends LoadEvent { ... }
class PullToRefreshEvent extends LoadEvent { ... }
on<LoadEvent>((event, emit) {
if (event is LoadPostsEvent)
});
or, in order to minimize code repetition, declare this event handler as a function
on<LoadPostsEvent>(_loadEvent);
on<PullToRefreshEvent>(_loadEvent);
...
void _loadEvent(PostsEvent event, Emitter<PostsState> emit) {
...
}

Provider notify other listeners from a ChangeNotifier

I have the following setup:
App with multiple pages: VoltagesPage & TemperaturesPage
I will receive some data over bluetooth serial like so:
bluetooth_data_provider.dart
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/widgets.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
class BluetoothDataProvider with ChangeNotifier {
String _data = '';
String get data => _data;
String _messageBuffer = '';
BluetoothConnection? _connection;
connectAndListen(String address) {
BluetoothConnection.toAddress(address).then((connection) {
print('Connected to the device');
_connection = connection;
if (_connection == null) {
return;
}
_connection!.input!.listen(_onDataReceived).onDone(() {});
}).catchError((error) {
print('Cannot connect, exception occured');
print(error);
});
}
void _onDataReceived(Uint8List data) {
String dataStr = ascii.decode(data);
_messageBuffer += dataStr;
if (dataStr.contains('\n')) {
print(_messageBuffer);
_data = _messageBuffer.substring(0, _messageBuffer.length - 1);
_messageBuffer = '';
notifyListeners();
}
}
}
This will notify all listeners, that listen to BluetoothDataProvider's changes to its data field.
Now the TemperaturesPage is not really interested in a message, that is meant for the VoltagesPage and vice versa. I can identify who should receive the message by a prefix that is being sent by the device.
A voltage message can look like this:
V:+12.5
A temperature message can look like this:
T:+45.6
Right now when I'm watching the BluetoothDataProvider, from both of the pages, the widget has to decide whether to accept the message, or not. But this might leave a widget hanging, because it still needs to rebuild the widget as build is being called.
What I really want is something like this:
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/widgets.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
class BluetoothDataProvider with ChangeNotifier {
String _data = '';
String get data => _data;
String _messageBuffer = '';
BluetoothConnection? _connection;
connectAndListen(String address) {
BluetoothConnection.toAddress(address).then((connection) {
print('Connected to the device');
_connection = connection;
if (_connection == null) {
return;
}
_connection!.input!.listen(_onDataReceived).onDone(() {});
}).catchError((error) {
print('Cannot connect, exception occured');
print(error);
});
}
void _onDataReceived(Uint8List data) {
String dataStr = ascii.decode(data);
_messageBuffer += dataStr;
if (dataStr.contains('\n')) {
print(_messageBuffer);
_data = _messageBuffer.substring(0, _messageBuffer.length - 1);
_messageBuffer = '';
if(_data.startsWith("T:")) {
// notify with a TemperatureProvider
} else if (_data.startsWith("V:")){
// notify with a VoltageProvider
}
}
}
}
That way, each page could listen to a different Provider and only receive data they are actually interested in.
Is a scenario like this possible?
Thanks!
I solved this now.
In my main.dart, I will just create an instance of BluetoothDataProvider in initState() and providing the context from there to the constructor of BluetoothDataProvider.
After that, I am free to use the context to call different providers as follows:
void _onDataReceived(Uint8List data) {
String dataStr = ascii.decode(data);
_messageBuffer += dataStr;
if (dataStr.contains('\n')) {
print(_messageBuffer); // here you get complete string
_data = _messageBuffer.substring(0, _messageBuffer.length - 1);
_messageBuffer = ''; //clear buffer to accept new string
var messageType = _data.substring(0, 2);
switch (messageType) {
case "V:":
context.read<CurrentReceivedMessage>().setMessage(_data);
break;
case "T:":
context.read<TemperatureReceivedMessage>().setMessage(_data);
break;
}
}
}
I think you can use Selector.
Each page can listens to the data that they only interested in listening to.