I've written a Flutter App that use the Bloc Pattern and the GetIt package. It's really weird that the app works fine on Android device but on an iOS Simulator, it only shows a black screen...
Here my code:
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
AppInitializer.init();
runApp(App());
}
app_initializer.dart
import 'package:authentication_repository/authentication_repository.dart';
import 'package:get_it/get_it.dart';
import 'package:techniciens/blocs/blocs.dart';
import 'package:user_repository/user_repository.dart';
class AppInitializer {
static final getIt = GetIt.instance;
static void init() {
_registerSingletons();
_registerBlocSingletons();
}
static void _registerSingletons() {
getIt.registerSingleton(AuthenticationRepository()..user.first);
getIt.registerLazySingleton<UserRepository>(() => UserRepository());
}
static void _registerBlocSingletons() {
getIt.registerLazySingleton<AuthenticationBloc>(() => AuthenticationBloc(
authenticationRepository:
GetIt.instance.get<AuthenticationRepository>()));
getIt.registerLazySingleton<SettingsFormCubit>(() => SettingsFormCubit());
getIt.registerLazySingleton<UserInfoCubit>(() => UserInfoCubit());
}
}
app.dart
import 'package:authentication_repository/authentication_repository.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:overlay_support/overlay_support.dart';
import 'package:techniciens/blocs/blocs.dart';
import 'package:techniciens/core/constants/constants.dart';
import 'package:techniciens/screens/screens.dart';
import 'package:get_it/get_it.dart';
import 'package:user_repository/user_repository.dart';
import 'core/theme/app_theme.dart';
class App extends StatelessWidget {
final AuthenticationRepository _authenticationRepository =
GetIt.instance.get<AuthenticationRepository>();
final UserRepository _userRepository = GetIt.instance.get<UserRepository>();
final AuthenticationBloc _authenticationBloc =
GetIt.instance.get<AuthenticationBloc>();
App({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiRepositoryProvider(
providers: [
RepositoryProvider.value(value: _authenticationRepository),
RepositoryProvider.value(value: _userRepository),
],
child: BlocProvider<AuthenticationBloc>.value(
value: _authenticationBloc,
child: const AppView(),
),
);
}
}
class AppView extends StatefulWidget {
const AppView({Key? key}) : super(key: key);
#override
State<AppView> createState() => _AppViewState();
}
class _AppViewState extends State<AppView> {
final _navigatorKey = GlobalKey<NavigatorState>();
NavigatorState get _navigator => _navigatorKey.currentState!;
#override
Widget build(BuildContext context) {
return DynamicColorBuilder(
builder: (ColorScheme? lightColorScheme, ColorScheme? darkColorScheme) {
return OverlaySupport.global(
child: MaterialApp(
title: kLoginAppbarTitle,
theme: AppTheme.lightTheme(lightColorScheme),
darkTheme: AppTheme.darkTheme(darkColorScheme),
themeMode: ThemeMode.system,
debugShowCheckedModeBanner: false,
navigatorKey: _navigatorKey,
builder: (context, child) {
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (_, state) {
switch (state.authStatus) {
case AuthenticationStatus.unauthenticated:
_navigator.pushAndRemoveUntil<void>(
LoginPage.route(), (route) => false);
break;
case AuthenticationStatus.authenticated:
_navigator.pushAndRemoveUntil<void>(
HomePage.route(), (route) => false);
break;
default:
break;
}
},
child: child,
);
},
onGenerateRoute: (_) => SplashPage.route(),
),
);
},
);
}
}
splash_page.dart
import 'package:flutter/material.dart';
class SplashPage extends StatelessWidget {
static Route<void> route() {
return MaterialPageRoute<void>(builder: (_) => const SplashPage());
}
const SplashPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator.adaptive(strokeWidth: 5),
),
);
}
}
As I could see in the Flutter Inspector and with some print commands, the apps runs normally until the BlocListener and falls on the onGenerateRoute.
The black screen
It gives me this
Launching lib/main.dart on iPhone 14 Pro in debug mode...
Running Xcode build...
Xcode build done. 13,8s
GrMtlCommandBuffer: WARNING: Creating MTLCommandBuffer while in background.
GrMtlCommandBuffer: WARNING: Creating MTLCommandBuffer while in background.
Debug service listening on ws://127.0.0.1:52638/_tIC8TChUyE=/ws
Syncing files to device iPhone 14 Pro...
flutter: dynamic_color: Dynamic color not detected on this device.
I've erased all data on the iOS Simulator, it doesn't give anything...
On Android Simulator, the code works super fine, but here, it fails every time on the Splash Screen.
Thank you for your help!
Related
Im following the documentation integrating AppRouter to my app
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() => runApp(App());
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
final _router = AppRouter();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
onGenerateRoute: _router.onGenerateRoute,
);
}
#override
void dispose() {
_router.dispose();
super.dispose();
}
}
Now in my router I need to use my bloc from my app instead of the counterbloc. This is the documentations code:
class AppRouter {
final _counterBloc = CounterBloc();
Route onGenerateRoute(RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(
builder: (_) => BlocProvider.value(
value: _counterBloc,
child: HomePage(),
),
);
case '/counter':
return MaterialPageRoute(
builder: (_) => BlocProvider.value(
value: _counterBloc,
child: CounterPage(),
),
);
default:
return null;
}
}
void dispose() {
_counterBloc.close();
}
}
In my app i have a LiveeventsBloc
...
BlocProvider<LiveeventsBloc>.value(
value: LiveeventsBloc(
RepositoryProvider.of<EventRepository>(_),
RepositoryProvider.of<SocketRepository>(_),
),
...
and I can't dispose it here since it needs two arguments.
this is the beginning of my bloc:
class LiveeventsBloc extends Bloc<LiveeventsEvent, LiveeventsState> {
final EventRepository _eventRepository;
final SocketRepository _socketRepository;
late final StreamSubscription eventStatusSubscription;
LiveeventsBloc(this._eventRepository, this._socketRepository)
: super(LiveeventsInitial()) {
...
How to dispose it in the approuter file? I dont have the context, and if i put a _ I also have an error
The problem is that I would like to show a loading indicator when the user tries to fetch some data from an api. But when the user presses the button, loading indicator shows once. But I would like to show the loading indicator every time when the user tries to fetch. It works but as I say It works once. Could anyone have any idea what can cause this problem? Here's the minimal code to reproduce the issue:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => HomeCubit()),
],
child: const MaterialApp(
title: 'Flutter Bloc Demo',
home: HomeView(),
),
);
}
}
class HomeView extends BaseView<HomeCubit, HomeState> {
const HomeView({Key? key}) : super(key: key);
#override
Widget builder(HomeCubit cubit, HomeState state) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(state.count.toString()),
ElevatedButton(
onPressed: cubit.increment,
child: const Text('Increase'),
),
],
),
);
}
}
class HomeState extends BaseState {
final int count;
HomeState({required this.count});
HomeState copyWith({
int? count,
}) {
return HomeState(
count: count ?? this.count,
);
}
}
class HomeCubit extends BaseCubit<HomeState> {
HomeCubit() : super(HomeState(count: 0));
void increment() {
flow(() async {
await Future.delayed(const Duration(seconds: 1));
emit(state.copyWith(count: state.count + 1));
});
}
}
#immutable
abstract class BaseView<C extends StateStreamable<S>, S extends BaseState>
extends StatelessWidget {
const BaseView({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) {
return BaseCubit(context.read<S>());
},
child: Scaffold(
body: BlocBuilder<C, S>(
builder: (context, state) {
final cubit = context.read<C>();
if (state.loadingState == LoadingState.loading) {
return loadingWidget;
}
return builder.call(cubit, state);
},
),
),
);
}
Widget builder(C cubit, S state);
Widget get loadingWidget => const Center(
child: CircularProgressIndicator(),
);
}
enum LoadingState { initial, loading, loaded }
class BaseState {
LoadingState loadingState;
BaseState({
this.loadingState = LoadingState.initial,
});
}
class BaseCubit<S extends BaseState> extends Cubit<S> {
BaseCubit(S state) : super(state);
Future<void> flow(Future<void> Function() function) async {
state.loadingState = LoadingState.loading;
emit(state);
await function();
state.loadingState = LoadingState.loaded;
emit(state);
}
}
Is it overengineering? I don't think you are duplicating much code if you just use BlocBuilder instead of some base class.
If bloc already exist you should provide it by BlocProvider.value instead of BlocProvider(create: read())
You should use context.watch instead of context.read to get a new value every time the state changes. context.read receives state only once.
It's overengineering, please take a look at https://bloclibrary.dev/#/coreconcepts. There are enough tutorials to catch the basic idea.
Then try to use bloc + freezed. Here is an example https://dev.to/ptrbrynt/why-bloc-freezed-is-a-match-made-in-heaven-29ai
I tried to follow the answer to this question, but I was not able to make it work.
I reproduced my issue on the counter app, changing it as follow.
main.dart
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (BuildContext ctx) => DummyCubit(),
),
],
child: MaterialApp(
...
}
class _MyHomePageState extends State<MyHomePage> {
...
#override
Widget build(BuildContext context) {
return Scaffold(
...
body: Center(
child: CounterViewer(counter: _counter),
),
...
);
}
}
class CounterViewer extends StatelessWidget {
const CounterViewer({required this.counter, Key? key}) : super(key: key);
final int counter;
#override
Widget build(BuildContext context) {
return BlocBuilder<DummyCubit, AState>(
builder: (ctx, state) => (state is! StateLoaded)
? const CircularProgressIndicator()
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
);
}
}
dummy_cubit.dart
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
class DummyCubit extends Cubit<AState> {
DummyCubit() : super(const InitState());
Future<void> executeLogic() async {
emit(const StateLoading());
// do some logic
emit(StateLoaded('some data'));
}
}
#immutable
abstract class AState {
const AState();
}
class InitState extends AState {
const InitState();
}
class StateLoading extends AState {
const StateLoading();
}
class StateLoaded extends AState {
const StateLoaded(this.data);
final String data;
#override
String toString() => data.toString();
#override
bool operator ==(Object other) =>
identical(this, other) ||
(other is StateLoaded &&
runtimeType == other.runtimeType &&
data == other.data);
#override
int get hashCode => data.hashCode;
}
widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:bloc_test/bloc_test.dart';
import 'package:mocktail/mocktail.dart' as mocktail;
import 'package:counter/dummy_cubit.dart';
import 'package:counter/main.dart';
class MockDummyCubit extends MockCubit<AState> implements DummyCubit {}
class AStateFake extends Fake implements AState {}
final dummyCubit = MockDummyCubit();
Widget get counter => MultiBlocProvider(
providers: [
BlocProvider<DummyCubit>(
create: (BuildContext ctx) => dummyCubit,
),
],
child: const MaterialApp(
home: CounterViewer(counter: 1),
),
);
void main() {
setUpAll(() {
mocktail.registerFallbackValue(AStateFake());
});
group('Counter viewer', () {
mocktail.when(() => dummyCubit.state).thenReturn(InitState());
testWidgets('should build', (WidgetTester tester) async {
await tester.pumpWidget(counter);
});
});
}
When running the test, I get this error:
The following StateError was thrown running a test:
Bad state: No method stub was called from within `when()`. Was a real method called, or perhaps an
extension method?
And removing the mocktail.when line, I get this error:
The following _TypeError was thrown building CounterViewer:
type 'Null' is not a subtype of type 'AState'
How do I solve this issue?
How do I control which state is emitted by my DummyCubit?
After reading this, I found the solution
class MockDummyCubit extends MockCubit<AState> implements DummyCubit {}
class AStateFake extends Fake implements AState {}
void main() {
late MockDummyCubit dummyCubit;
setUpAll(() {
mocktail.registerFallbackValue(AStateFake());
});
setUp(() {
dummyCubit = MockDummyCubit();
mocktail.when(() => dummyCubit.state).thenReturn(const InitState());
});
group('Counter viewer', () {
testWidgets('should build', (WidgetTester tester) async {
await tester.pumpWidget(getCounter(dummyCubit));
});
});
}
Widget getCounter(MockDummyCubit dummyCubit) => MultiBlocProvider(
providers: [
BlocProvider<DummyCubit>(
create: (BuildContext ctx) => dummyCubit,
),
],
child: const MaterialApp(
home: CounterViewer(counter: 1),
),
);
in my app i want to detect in the splashscreen if this app is started for the first time.
For that i want to use the hive nosql package.
After that if the app is started for the first time it will open the welcome page and if not the login page.
main.dart
import 'package:flutter_config/flutter_config.dart';
import 'package:flutter/material.dart';
import 'package:app/pages/splash/splash_page.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'config/theme/theme.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterConfig.loadEnvVariables();
await Hive.initFlutter();
await Hive.openBox('settings');
runApp(const App());
}
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
void dispose() async {
Hive.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App',
debugShowCheckedModeBanner: false,
theme: lightThemeData(context),
darkTheme: darkThemeData(context),
home: const SplashPage(),
);
}
}
splash_page.dart
import 'package:flutter/material.dart';
import 'package:app/pages/login/login_page.dart';
import 'package:app/pages/welcome/welchome_page.dart';
import 'package:app/services/settings_service.dart';
class SplashPage extends StatefulWidget {
const SplashPage({Key? key}) : super(key: key);
#override
State<SplashPage> createState() => _SplashPageState();
}
class _SplashPageState extends State<SplashPage> {
#override
Widget build(BuildContext context) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) =>
Settings().isFirstTime ? const WelcomePage() : const LoginPage(),
),
);
return const Scaffold(
body: Center(
child: SizedBox(
width: 125,
height: 125,
child: Icon(Icons.clear),
),
),
);
}
}
there i call the function "var _isFirstTime = Settings().isFirstTime;" it should return me a bool
settings_service.dart
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
class Settings {
final Box _settingsStorage = Hive.box('settings');
get isFirstTime {
if (_settingsStorage.get('firstRun')) {
return true;
} else {
_settingsStorage.put('firstRun', true);
}
return false;
}
}
i got this error:
════════ Exception caught by widgets library ═══════════════════════════════════
The following _TypeError was thrown building SplashPage(dirty, state: _SplashPageState#261ae):
type 'Null' is not a subtype of type 'bool'
how can i solve this? later i would like to use the settings service for other settings as well ...
In the setting_service.dart,
I think this line -> _settingsStorage.get('firstRun'), is returning null. From what I understand what you should do is that whenever you get firstRun as null, you should assign it true.
if (_settingsStorage.get('firstRun') ?? true) {
return _settingsStorage.get('firstRun') ?? true;
}
I am new to Flutter, and bloc too. I got the idea, how bloc works. But When I create a simple app as the first step of my note app. The bloc doesn't give the data. This simple app has two screens. list screen and Notedetailscreen. Button in NoteDetailScreen tapped, data does not print to the text widget.
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:note_demo_bloc/bloc/note_bloc.dart';
import 'package:note_demo_bloc/list_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider<NoteBloc>(
create: (context) => NoteBloc(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ListScreen(),
),
);
}
}
note_bloc.dart
import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:meta/meta.dart';
part 'note_event.dart';
part 'note_state.dart';
class NoteBloc extends Bloc<NoteblocEvent, NoteblocState> {
NoteBloc() : super(NoteblocInitial());
#override
Stream<NoteblocState> mapEventToState(
NoteblocEvent event,
) async* {
if (event == NoteSaveEvent) {
yield NoteSaveState(state);
}
}
}
part of 'note_bloc.dart';
#immutable
abstract class NoteblocEvent {}
class NoteSaveEvent extends NoteblocEvent {
NoteSaveEvent(this.text);
final text;
}
note_state.dart
part of 'note_bloc.dart';
#immutable
abstract class NoteblocState {}
class NoteblocInitial extends NoteblocState {}
class NoteSaveState extends NoteblocState {
NoteSaveState(this.text);
final text;
}
list_screen.dart
import 'package:flutter/material.dart';
import 'package:note_demo_bloc/note_detail_screen.dart';
class ListScreen extends StatefulWidget {
const ListScreen({Key? key}) : super(key: key);
#override
_ListScreenState createState() => _ListScreenState();
}
class _ListScreenState extends State<ListScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Text('hi'),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => NoteDetailScreen(),
),
);
},
),
);
}
}
Note_detailscreen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:note_demo_bloc/bloc/note_bloc.dart';
class NoteDetailScreen extends StatefulWidget {
const NoteDetailScreen({Key? key}) : super(key: key);
#override
_NoteDetailScreenState createState() => _NoteDetailScreenState();
}
class _NoteDetailScreenState extends State<NoteDetailScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: () {
BlocProvider.of<NoteBloc>(context).add(NoteSaveEvent('hi'));
},
child: Text('click'),
),
BlocBuilder<NoteBloc, NoteblocState>(
builder: (context, state) {
return Text(state.toString());
},
)
],
),
);
}
}
Your bloc, state, and event looks fine. When you push screen you might need to use BlocProvider again. So try this:
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:note_demo_bloc/bloc/note_bloc.dart';
import 'package:note_demo_bloc/list_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
NoteBloc _noteBloc = NoteBloc();
#override
Widget build(BuildContext context) {
return BlocProvider<NoteBloc>(
create: (context) => _noteBloc(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ListScreen(),
),
);
}
}
list_screen.dart
import 'package:flutter/material.dart';
import 'package:note_demo_bloc/note_detail_screen.dart';
class ListScreen extends StatefulWidget {
const ListScreen({Key? key}) : super(key: key);
#override
_ListScreenState createState() => _ListScreenState();
}
class _ListScreenState extends State<ListScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Text('hi'),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => BlocProvider.value(value: BlocProvider.of<NoteBloc>(context), child: NoteDetailScreen()),
),
);
},
),
);
}
}
Note_detailscreen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:note_demo_bloc/bloc/note_bloc.dart';
class NoteDetailScreen extends StatefulWidget {
const NoteDetailScreen({Key? key}) : super(key: key);
#override
_NoteDetailScreenState createState() => _NoteDetailScreenState();
}
class _NoteDetailScreenState extends State<NoteDetailScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
ElevatedButton(
onPressed: () {
BlocProvider.of<NoteBloc>(context).add(NoteSaveEvent('hi'));
},
child: Text('click'),
),
BlocBuilder<NoteBloc, NoteblocState>(
bloc: BlocProvider.of<NoteBloc>(context),
builder: (context, state) {
return Text(state.toString());
},
)
],
),
);
}
}
So, this is not an answer of your question but consider that as alternative (for future users of SO).
As state management is a free choice, and everyone could manage that as it’s “modus operandi“ this helper class “home made” could be a good choice.
import 'dart:async';
import 'dart:core';
class Method {
Method(this.name, this.params);
final String name;
final Map<String, Object> params;
}
class _Controller {
_Controller._();
static final Map<String, _Controller> _this = new Map<String, _Controller>();
final Map<String, Function(Method)> _funcs = new Map<String, Function(Method)>();
factory _Controller(String identifier) => _this.putIfAbsent(identifier, () => _Controller._());
Future<void> activateListener(String listenerId, Function(Method) function) async {
if (function != null)
_funcs.containsKey(listenerId) ? _funcs[listenerId] = function : _funcs.putIfAbsent(listenerId, () => function);
}
Future<void> deactivateListener(String listenerId) async =>
_funcs.removeWhere((String key, Function(Method) func) => key == listenerId);
Future<void> removeListener(String identifier) async =>
_this.removeWhere((String key, _Controller mClass) => key == identifier);
Future<void> callMethod(String methodName, {Map<String, Object> params}) async =>
Future.forEach(_funcs.values.where((v) => v != null), (func) async => func.call(Method(methodName, params)));
}
mixin MethodListener on Object {
_Controller _getController(String identifier) => _Controller(identifier ?? this.runtimeType.toString());
Future<void> activateListener({String identifier, List<String> identifiers}) async {
if (identifiers != null && identifiers.length > 0)
identifiers.forEach(
(String currentId) => _getController(currentId).activateListener(this.hashCode.toString(), onMethodListener));
else
_getController(identifier).activateListener(this.hashCode.toString(), onMethodListener);
}
Future<void> deactivateListener({String identifier, List<String> identifiers}) async {
if (identifiers != null && identifiers.length > 0)
identifiers.forEach((String currentId) => _getController(currentId).deactivateListener(this.hashCode.toString()));
else
_getController(identifier).deactivateListener(this.hashCode.toString());
}
Future<void> removeListener({String identifier}) async => _getController(identifier).removeListener(identifier);
void onMethodListener(Method method) async => null;
Future<void> callMethodOn(String identifier, String methodName, {Map<String, Object> params}) async =>
_getController(identifier).callMethod(methodName, params: params);
}
class MethodManager with MethodListener {
MethodManager._();
static MethodManager _this;
factory MethodManager() {
if (_this == null) _this = MethodManager._();
return _this;
}
Future<void> callMethodOnWidgets(List<String> identifiers, String methodName, {Map<String, Object> params}) async =>
identifiers.forEach((String currentId) => callMethodOn(currentId, methodName, params: params));
#override
Future<void> callMethodOn(String identifier, String methodName, {Map<String, Object> params}) async =>
super.callMethodOn(identifier, methodName, params: params);
}
then you can implements classes with “with MethodListener” as follows:
import 'package:flutter/material.dart';
import 'package:yourpackagehere/utils/XMethods.dart';
class Test extends StatefulWidget {
static const String NAME = "Test";
#override
createState() => _TestState();
}
class _TestState extends State<Test> with MethodListener {
String _ciao;
#override
void initState() {
super.initState();
this.activateListener(identifier: Test.NAME);
}
#override
void dispose() {
this.deactivateListener(identifier: Test.NAME);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(child: Text(_ciao));
}
#override
void onMethodListener(Method method) {
switch (method.name) {
case "say_hello":
if (mounted) {
setState(() {
_ciao = method.params["my_string"];
});
}
break;
}
}
}
Usage:
From everywhere (from widgets or classes):
MethodManager().callMethodOn(Test.NAME, "say_hello", params: {"my_string": "SIAMO CAMPIONI DI EUROPA!!!"});