Trying to build a dynamic app that can change its own environment within itself.
Scenario:
ConfigBloc handles config state changes ( ApiConfig, theme, and locale) on top of everything.MaterialApp build on ConfigBloc state.AuthenticationBloc (depend on AuthenticationRepository) provided for all MaterialApp children .AuthenticationRepository (depend on Protoclient).Protoclient (depend onApiConfigandLocale` for host and error message).Problem:
Theme and localisations works well, but
AuthenticationRepositorywon't change with newProtoclient.
Question:
Is it a wrong way to use this bloc_pattern? or is there a technique to achieve this?
Hi @nielstj 馃憢
Thanks for opening an issue!
This is because BlocProvider is a StatefulWidget under the hood and its builder is only called once (think initState). If you want ProtoClient to have a different configuration based on changes in ConfigBloc you can refactor ProtoClient to have a dependency on ConfigBloc like:
import 'dart:async';
import 'package:basebloc/blocs/app_config_bloc/config_bloc.dart';
import 'package:basebloc/generated/i18n.dart';
import 'package:meta/meta.dart';
import 'package:http/http.dart' as http;
import 'package:basebloc/models/models.dart';
class Protoclient {
const Protoclient({
@required this.configBloc,
@required this.client,
@required this.local,
}) : assert(configBloc != null),
assert(client != null),
assert(local != null);
final ConfigBloc configBloc;
final http.Client client;
final I18n local;
ApiConfig get config => configBloc.state.apiConfig;
Uri toServeUri(String path) => Uri(
host: config.baseUrl,
path: path,
port: config.port,
scheme: config.scheme);
Future<String> call() async {
await Future.delayed(Duration(seconds: 1));
return local.greetTo('From Login');
}
@override
String toString() => '''Protoclient: {
$config,
$client,
$local
}''';
}
Then you can simplify your UI:
import 'package:basebloc/Repositories/authentication_repository.dart';
import 'package:basebloc/app/router/router.dart';
import 'package:basebloc/generated/i18n.dart';
import 'package:basebloc/models/models.dart';
import 'package:basebloc/network_client/protoclient.dart';
import 'package:flutter/material.dart';
import 'package:basebloc/blocs/blocs.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:http/http.dart' as http;
class BaseApp extends StatelessWidget {
@override
Widget build(BuildContext context) => BlocListener<ConfigBloc, ConfigState>(
listener: (context, state) {
I18n.locale = state.locale;
},
child: BlocBuilder<ConfigBloc, ConfigState>(
builder: (context, state) {
return MaterialApp(
onGenerateRoute: AppRouter.router.generator,
localizationsDelegates: [
I18n.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate
],
supportedLocales: I18n.delegate.supportedLocales,
theme: state.themeStyle == AppThemeStyle.light
? ThemeData.light()
: ThemeData.dark(),
builder: (context, widget) {
return BlocProvider<AuthenticationBloc>(
builder: (_) {
return AuthenticationBloc(
authRepository: AuthenticationRepository(
client: Protoclient(
configBloc: BlocProvider.of<ConfigBloc>(context),
client: http.Client(),
local: I18n.of(context),
),
),
)..add(CheckLogStatus());
},
child: widget,
);
},
);
},
),
);
}
Let me know if that helps 馃憤
Thanks for such fast response! Yes, it works and much cleaner now.
Localisations is still stuck though, I guess it's a bad practice to inject localisations to api client class?
Any tips for localisations with flutter_bloc?
Awesome! Glad I could help 馃槃
Can you describe why you need localizations in the API client?
My intention is to localised the exception / bad response from the api call. so i can catch it in bloc then map to state.failure(message) directly.
if (response.statusCode == 200) {
return body;
} else if (response.statusCode == 400) {
local.networkFailInvalidRequest();
} else if (response.statusCode == 401) {
throw AppException(message: local.networkFailUnauthorizedRequest);
} else if (response.statusCode == 500) {
throw AppException(message: local.networkFailInternalServerError);
} else {
throw AppException(message: local.networkFailUnknownError);
}
} on TimeoutException {
throw AppException(message: local.networkFailRequestTimeOut);
} on SocketException {
throw AppException(message: local.networkFailServerUnreachable);
}
...
Thanks for the clarification. I would recommend that you do the localization on the server if possible in order to simplify the client code and to make it more flexible (you can change translations without a new client release). If that鈥檚 not possible then you鈥檒l need to do the localization in the widgets. Blocs and API clients should not be responsible for localization because it is a presentation detail. You can map the status codes to an enum in the bloc鈥檚 error state and switch on the enum in the widget to handle the localization.
Hope that helps 馃憤
Yes, agree. Thanks for the solution!
Most helpful comment
Yes, agree. Thanks for the solution!