I need a good way to handle errors while I'm Using Dio requests.
Can I do it in one class and pass the dio request throw it ?
and it should return a response with the error .
I am posting my generalized network bloc here, which can be reused any number of time, any where. also, It uses dio using API repository , exceptional and error handling.
class NetworkBloc extends Bloc<NetworkEvent, NetworkState> {
NetworkBloc() : super(NetworkRequestInitial());
#override
Stream<NetworkState> mapEventToState(
NetworkEvent event,
) async* {
yield NetworkRequestInitiated();
if (event is NetworkCallEvent) {
RequestType requestType = event.requestType;
if (requestType == RequestType.GET) {
yield* fetchData(event);
} else if (requestType == RequestType.POST) {
yield* uploadDataAndStoreResult(event);
}
}
}
Stream<NetworkState> fetchData(NetworkCallEvent event) async* {
Response response;
try {
yield NetworkRequestLoading();
response =
await event.apiRepository.sendGetRequest(event.url, event.request);
if (response.statusCode == 200) {
yield NetworkRequestLoaded(response: response);
} else {
Map jsonResponse = jsonDecode(response.data);
yield NetworkRequestFailure(message: jsonResponse['message']);
}
} catch (e) {
yield NetworkRequestFailure(
message: NetworkUtils.getErrorMessageAccordingToError(e));
}
}
Stream<NetworkState> uploadDataAndStoreResult(NetworkCallEvent event) async* {
Response response;
try {
yield NetworkRequestLoading();
if (event.request != null) {
if (event.isHeadersNeeded) {
response = await event.apiRepository.sendPostRequestWithHeader(
event.url,
request: event.request,
);
} else {
response = await event.apiRepository.sendPostRequest(
event.url,
event.request,
);
}
} else {
response = await event.apiRepository
.sendPostRequestWithoutBodyParameters(event.url);
}
if (response.statusCode == 200) {
saveDataAccordingToCacheMechanism(event, response);
yield NetworkRequestLoaded(response: response);
} else {
Map jsonResponse = jsonDecode(response.data);
yield NetworkRequestFailure(message: jsonResponse['message']);
}
} catch (e) {
yield NetworkRequestFailure(
message: NetworkUtils.getErrorMessageAccordingToError(e));
}
}
void saveDataAccordingToCacheMechanism(
NetworkCallEvent event, Response response) async {
if (event.cacheMechanism == CacheMechanism.SharePreferences) {
Hive.box(ConstUtils.dbName)
.put(event.keyForSharedPreferences, response.data.toString());
} else if (event.cacheMechanism == CacheMechanism.Database) {}
}
}
I am also adding states and events to make it more easy to understand.
class NetworkCallEvent extends NetworkEvent {
final String request;
final dynamic url;
final RequestType requestType;
final CacheMechanism cacheMechanism;
final String keyForSharedPreferences;
final APIRepository apiRepository;
final bool isHeadersNeeded;
NetworkCallEvent(
{#required this.url,
this.request,
this.isHeadersNeeded = false,
#required this.requestType,
#required this.apiRepository,
#required this.cacheMechanism,
this.keyForSharedPreferences});
#override
List<Object> get props => [
this.url,
this.request,
this.requestType,
this.cacheMechanism,
this.keyForSharedPreferences,
this.apiRepository
];
}
Network_states:
class NetworkRequestInitial extends NetworkState {}
class NetworkRequestInitiated extends NetworkState {}
class NetworkRequestLoading extends NetworkState {}
class NetworkRequestLoaded extends NetworkState {
final dynamic response;
NetworkRequestLoaded({this.response});
#override
List<Object> get props => [this.response];
}
class NetworkRequestFailure extends NetworkState {
final String message;
NetworkRequestFailure({this.message});
#override
List<Object> get props => [this.message];
}
You can easily send request in JSON and get Response in dynamic, which you can convert to appropriate object using json.decode().
Related
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;
});
I'm trying to recieve a list from a Sql Api. The catch is that i need to give an id with the query. the Widget.klant.klantId has the value i need. i know it has somthing to do with the as List<Machine> in accountpage.dart. Hope you can help me with this problem. thanks in advance.
The hole error:
accountpage.dart:
class Accountpage extends StatefulWidget {
const Accountpage(this.klant);
final Klant klant;
#override
_AccountpageState createState() => _AccountpageState();
}
class _AccountpageState extends State<Accountpage> {
_AccountpageState();
final ApiService api = ApiService();
late List<Machine> machineList;
#override initState(){
super.initState();
_getMachine();
machineList = [];
}
void _getMachine() async{
machineList = (await ApiService().getMoreMachine(widget.klant.klantId.toString())) as List<Machine>;
Future.delayed(const Duration(seconds: 1)).then((value) => setState(() {}));
}
#override
Widget build(BuildContext context) {
//Here starts the body
api_machine.dart:
Future<Machine> getMoreMachine(String klantId) async {
final response = await get(Uri.parse('$apiUrl/Select/$klantId'));
if (response.statusCode == 200) {
return Machine.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load a case');
}
}
MachineModel.dart:
List<Machine> welcomeFromJson(String str) => List<Machine>.from(json.decode(str).map((x) => Machine.fromJson(x)));
String welcomeToJson(List<Machine> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class Machine {
Machine({
this.serienummerId,
this.serienummer,
this.bouwjaar,
this.urenstand,
this.locatie,
this.klantId,
});
int? serienummerId;
String? serienummer;
String? bouwjaar;
String? urenstand;
String? locatie;
String? klantId;
factory Machine.fromJson(Map<String, dynamic> json) => Machine(
serienummerId: json["SerienummerId"],
serienummer: json["Serienummer"],
bouwjaar: json["Bouwjaar"],
urenstand: json["Urenstand"],
locatie: json["Locatie"],
klantId: json["KlantId"],
);
Map<String, dynamic> toJson() => {
"SerienummerId": serienummerId,
"Serienummer": serienummer,
"Bouwjaar": bouwjaar,
"Urenstand": urenstand,
"Locatie": locatie,
"KlantId": klantId,
};
}
json result
[
{
"SerienummerId": 1,
"Serienummer": "-----",
"Bouwjaar": "2020",
"Urenstand": "10",
"Locatie": "---",
"KlantId": "1"
},
{
"SerienummerId": 2,
"Serienummer": "-----",
"Bouwjaar": "1998",
"Urenstand": "5010",
"Locatie": "----",
"KlantId": "1"
}
]
You are parsing the result as if it's a single Machine while it in fact is a list of machines. Process it as a list and also use the correct return type accordingly. Like
Future<List<Machine>> getMoreMachine(String klantId) async {
final response = await get(Uri.parse('$apiUrl/Select/$klantId'));
if (response.statusCode == 200) {
return List<Machine>.from(json.decode(response.body).map((x) => Machine.fromJson(x)));
} else {
throw Exception('Failed to load a case');
}
}
the return type of the method is Machine:
Future<Machine> getMoreMachine(String klantId) async {
final response = await get(Uri.parse('$apiUrl/Select/$klantId'));
if (response.statusCode == 200) {
return Machine.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load a case');
}
}
and then you cast a Machine to List :
machineList = (await ApiService()
.getMoreMachine(widget.klant.klantId.toString())) as List<Machine>;
I don't know what the JSON looks like... but if there is only one machine you could for example add it to a list like this:
machineList.add((await ApiService()
.getMoreMachine(widget.klant.klantId.toString())));
Update
Try this:
Future<List<Machine>> getMoreMachine(String klantId) async {
final response = await get(Uri.parse('$apiUrl/Select/$klantId'));
if (response.statusCode == 200) {
final jsonMachines = Machine.fromJson(json.decode(response.body));
return jsonMachines.map((item) => Machine.fromJson(item)).toList();
} else {
throw Exception('Failed to load a case');
}
}
I think this is because of in getMoreMachine you used return type as Machine actually you are assigning that value as List so make that change like this :
Future<List<Machine>> getMoreMachine(String klantId) async {
final response = await get(Uri.parse('$apiUrl/Select/$klantId'));
if (response.statusCode == 200) {
return welcomeFromJson(response.body);
} else {
throw Exception('Failed to load a case');
}
}
might be other think is you can check your API response that is not returning List of machines.
am learning api integration with bloc, these exception is been thrown when data is trying to fetch, for loadingstate i assigned a progressindicator then after that state when trying to get data,these exeption is been thrown ,pls helpenter image description here
as per the console i tried to change the data type to from double to num, still same exception
try {
_emitters.add(emitter);
await handler(event as E, emitter);
} catch (error, stackTrace) {
onError(error, stackTrace);
rethrow;
} finally {
onDone();
}
networkfile.dart
class Repository {
List<FakeStore> collections = [];
Future<List<FakeStore>?> getdata() async {
String url = 'https://fakestoreapi.com/products';
final data = await http.Client().get(Uri.parse(url));
if (data.statusCode != 200) {
return null;
} else {
Iterable values = jsonDecode(data.body);
for (var value in values) {
FakeStore fakeStore = FakeStore.fromJson(value);
collections.add(fakeStore);
}
return collections;
}
}
}
bloc.dart
class FakestoreBloc extends Bloc<FakestoreEvent, FakestoreState> {
final Repository repository;
FakestoreBloc({required this.repository}) : super(FakestoreInitialstate()) {
on<FakestoreEvent>((event, emit) async {
if (event is StorelaodEvent) {
emit(Fakestorelaodingstate());
List<FakeStore>? apiresult = await repository.getdata();
if (apiresult == null) {
emit(FAkestoreErrorstate());
} else {
emit(Fakestoreloadedstate(apiresult: apiresult));
}
}
});
}
}
I have a class in Flutter which retrieve device IP Address:
import 'package:http/http.dart' as https;
class CheckIp {
Future<String> getIP(String text) async {
try {
const url = 'https://api.ipify.org';
var response = await https.get(Uri.parse(url));
if (response.statusCode == 200) {
print(response.body);
return response.body;
} else {
print(response.body);
return null;
}
} catch (exception) {
print(exception);
return null;
}
}
}
and I call this class in the main file :
#override
void initState() {
CheckIp().getIP(ipAddress);
}
now what I want to do is to get from the class the the string relative to response.body
and pass this string into the main file
any suggestion?
First of all I would format the CheckIp class like this:
You are not actually using the text parameter, so why not deleting it?
The getIP() function can be static so you don't need to instantiate the CheckIp class.
Then, the method return a Future of type String, so you should return empty string instead of null to avoid errors.
class CheckIp {
static Future<String> getIP() async {
try {
const url = 'https://api.ipify.org';
var response = await https.get(Uri.parse(url));
if (response.statusCode == 200) {
print(response.body);
return response.body;
} else {
print(response.body);
return '';
}
} catch (exception) {
print(exception);
return '';
}
}
}
Then, for the main:
late ipAddress;
#override
void initState() async {
ipAddress = await CheckIp.getIP();
}
I want to implement GraphQL client in my flutter app. For Dependency injection, I use GetIt library. But when I run the app, it says
'Invalid argument (Object of type HomeGraphQLService is not
registered inside GetIt. Did you forget to pass an instance name?
(Did you accidentally do GetIt sl=GetIt.instance(); instead of GetIt
sl=GetIt.instance;)): HomeGraphQLService'
.
It means GraphQL client did not instantiate somehow, although I registered it in my service locator
Session.dart
abstract class Session {
String getAccessToken();
}
SessionImpl.dart
class SessionImpl extends Session {
SharedPreferences sharedPref;
SessionImpl(SharedPreferences sharedPref) {
this.sharedPref = sharedPref;
}
#override
String getAccessToken() {
return sharedPref.getString('access_token') ?? "";
}
}
GraphQLClientGenerator.dart
class GraphQLClientGenerator {
Session session;
GraphQLClientGenerator(Session session) {
this.session = session;
}
GraphQLClient getClient() {
final HttpLink httpLink = HttpLink('https://xxx/graphql');
final AuthLink authLink = AuthLink(getToken: () async => 'Bearer ${_getAccessToken()}');
final Link link = authLink.concat(httpLink);
return GraphQLClient(link: link, cache: GraphQLCache(store: InMemoryStore()));
}
String _getAccessToken() {
return session.getAccessToken();
}
}
HomeRepository.dart
abstract class HomeRepository {
Future<List<Course>> getAllCourseOf(String className, String groupName);
}
HomeRepositoryImpl.dart
class HomeRepositoryImpl extends HomeRepository {
HomeGraphQLService homeGraphQLService;
HomeMapper homeMapper;
HomeRepositoryImpl(HomeGraphQLService homeGraphQLService, HomeMapper homeMapper) {
this.homeGraphQLService = homeGraphQLService;
this.homeMapper = homeMapper;
}
#override
Future<List<Course>> getAllCourseOf(String className, String groupName) async {
final response = await homeGraphQLService.getAllCourseOf(className, groupName);
return homeMapper.toCourses(response).where((course) => course.isAvailable);
}
}
HomeGraphQLService.dart
class HomeGraphQLService {
GraphQLClient graphQLClient;
HomeGraphQLService(GraphQLClient graphQLClient) {
this.graphQLClient = graphQLClient;
}
Future<SubjectResponse> getAllCourseOf(String className, String groupName) async {
try {
final response = await graphQLClient.query(getAllCourseQuery(className, groupName));
return SubjectResponse.fromJson((response.data));
} catch (e) {
return Future.error(e);
}
}
}
GraphQuery.dart
QueryOptions getAllCourseQuery(String className, String groupName) {
String query = """
query GetSubject($className: String, $groupName: String) {
subjects(class: $className, group: $groupName) {
code
display
insights {
coming_soon
purchased
}
}
}
""";
return QueryOptions(
document: gql(query),
variables: <String, dynamic>{
'className': className,
'groupName': groupName,
},
);
}
ServiceLocator.dart
final serviceLocator = GetIt.instance;
Future<void> initDependencies() async {
await _initSharedPref();
_initSession();
_initGraphQLClient();
_initGraphQLService();
_initMapper();
_initRepository();
}
Future<void> _initSharedPref() async {
SharedPreferences sharedPref = await SharedPreferences.getInstance();
serviceLocator.registerSingleton<SharedPreferences>(sharedPref);
}
void _initSession() {
serviceLocator.registerLazySingleton<Session>(()=>SessionImpl(serviceLocator()));
}
void _initGraphQLClient() {
serviceLocator.registerLazySingleton<GraphQLClient>(() => GraphQLClientGenerator(serviceLocator()).getClient());
}
void _initGraphQLService() {
serviceLocator.registerLazySingleton<HomeGraphQLService>(() => HomeGraphQLService(serviceLocator()));
}
void _initMapper() {
serviceLocator.registerLazySingleton<HomeMapper>(() => HomeMapper());
}
void _initRepository() {
serviceLocator.registerLazySingleton<HomeRepository>(() => HomeRepositoryImpl(serviceLocator(), serviceLocator()));
}
main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown],
);
await initDependencies();
runApp(MyApp());
}
I cannot say where exactly it is happening because it is elsewhere in your code where you are accessing the GraphQLService, but the problem is definitely due to the lazy loading. The object has not been created and loaded by the locator before it is being accessed. Try updating ServiceLocator.dart to instantiate the classes during registration, like so:
void _initSession() {
serviceLocator.registerSingleton<Session>.(SessionImpl(serviceLocator()));
}
void _initGraphQLClient() {
serviceLocator.registerSingleton<GraphQLClient>(
GraphQLClientGenerator(serviceLocator()).getClient());
}
void _initGraphQLService() {
serviceLocator.registerSingleton<HomeGraphQLService>(
HomeGraphQLService(serviceLocator()));
}
void _initMapper() {
serviceLocator.registerSingleton<HomeMapper>(HomeMapper());
}
void _initRepository() {
serviceLocator.registerSingleton<HomeRepository>(
HomeRepositoryImpl(serviceLocator(), serviceLocator()));
}