getx
getx copied to clipboard
[proposal] add a Listener widget for reactions
Sometimes we need to perform some side-effects in response to some event, like: to open the dialog, to show the snackbar, to navigate between screens. We already have workers (MobX Reactions analogue) to call them in initState, watch some Rx variables and perform reactions on their value change.
Cons:
- workers are working with Rx types only
- we need to convert our screen to StatefulWidget, in order to have an initState lifecycle method
- sometimes it's more convenient to call methods from the widget tree
In Bloc we have useful BlocListener class: https://pub.dev/documentation/flutter_bloc/latest/flutter_bloc/BlocListener-class.html I feel lack of such a widget in Get. I propose to add the same widget. That's how it could look:
// for manual update
GetListener<MyController>(
listener: (c) {
if (c.count > 10) showDialog();
}
child: Container(),
)
// for rx update
RxListener(
listener: () {
if (count.value > 10) showDialog();
}
child: Container(),
)
Further development for this topic could be a MultiListener (to avoid multiple nested listeners): https://pub.dev/documentation/flutter_bloc/latest/flutter_bloc/MultiBlocListener-class.html
MultiGetListener(
listeners: [
GetListener<A>
listener: (c) {},
),
GetListener<B>
listener: (c) {},
),
RxListener
listener: () {},
),
RxListener
listener: () {},
),
],
child: Child(),
)
Yeah totally usefull for interactive page
It maybe more convenient if add this feature get_bloc
(it seems flutter_bloc, so I am just call it get_bloc) into GetX, but in my opinion this may bring in some side-effects or problems:
- More business logic will write in UI widgets not controller;
- How and when to dispose this listeners?
- Does this impact the lifecycle of controller?
I think this is not the original intention of GetX.
It maybe more convenient if add this feature
get_bloc
(it seems flutter_bloc, so I am just call it get_bloc) into GetX, but in my opinion this may bring in some side-effects or problems:
- More business logic will write in UI widgets not controller;
- How and when to dispose this listeners?
- Does this impact the lifecycle of controller?
I think this is not the original intention of GetX.
sometimes you just need small event to trigger some action, like display some ads if user scroll to index 5, adding Controller and GetBuilder for display single Ads is totally waste resource and make boilerplate as you can see flutter bloc and mobx have same feature and its dsnt make sense to not make same feature to GetX
they STILL part of the UI, but show only if certain condition is met like
...ListView(
GetListener<MyController>(
listener: (c) {
if (c.count > 10) showDialog();
}
child: Container(),
)
return Widget... bla bla bla
)
It maybe more convenient if add this feature
get_bloc
(it seems flutter_bloc, so I am just call it get_bloc) into GetX, but in my opinion this may bring in some side-effects or problems:
- More business logic will write in UI widgets not controller;
- How and when to dispose this listeners?
- Does this impact the lifecycle of controller?
Seems like you're not completely understand my point. It's not a Bloc (Bloc is a state automata, while GetX is a tool for granular rebuilding (closer to MobX)). I just proposed to add the same Listener widget as in Bloc library.
- Navigation / Dialogs / Snackbars is not a business-logic. It's a UI logic, and should be performed on a widget side. If you do navigation / showing dialogs inside of controllers, you make a mistake.
- No subscription added - no disposing needed. Workers (reactions) return a subcription. Listener is a widget, and it doesn't, so you don't need to dispose anything.
- No, it doesn't relate to controller in any way.
The truth is, this could be added using the NotificationListener widget and dispatching it inside getUpdate. It's not hard to do (actually 10 minutes of work). I only need to study performance implications. Obx does not work with InheritedWidget and is a very clean widget. GetX would be a widget that I wouldn't mind adding this to since it already has a number of checks that Obx doesn't use. This weekend I'm going to check that adding this won't create any additional overhead.
Maybe this package will help you: https://pub.dev/packages/nested
This weekend I'm going to check that adding this won't create any additional overhead.
@jonataslaw do you have any progress on this issue?
People in the chat also want this feature.
Hi, probably the next big version will have this. However, to implement the feature, it is necessary to decide whether the listener will be from reactive variables, or from controllers
Take and use. Good luck!
get_consumer.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class _GetConsumerListeners {
Map<Key, VoidCallback> listeners = {};
VoidCallback createListener(Key key, VoidCallback listener) {
listeners[key] = listener;
return listeners[key]!;
}
VoidCallback getListener(Key key) {
return listeners[key]!;
}
void removeListener(Key key) {
listeners.removeWhere((k, _) => k == key);
}
}
final _getConsumerListeners = _GetConsumerListeners();
class GetConsumer<T extends GetxController> extends GetBuilder<T> {
final void Function(T) listener;
GetConsumer({
Key? key,
required T init,
global = true,
required builder,
required this.listener,
autoRemove = true,
assignId = false,
void Function(GetBuilderState<T>)? initState,
filter,
tag,
dispose,
id,
didChangeDependencies,
didUpdateWidget,
}) : super(
key: key,
init: init,
global: global,
builder: builder,
autoRemove: autoRemove,
assignId: assignId,
id: id,
didChangeDependencies: didChangeDependencies,
didUpdateWidget: didUpdateWidget,
filter: filter,
tag: tag,
initState: (GetBuilderState<T> state) {
final consumerState = (state as GetConsumerState<T>);
consumerState.generateConsumerListenerKey();
VoidCallback _listener = _getConsumerListeners.createListener(
consumerState.consumerListenerKey,
() => listener(init),
);
init.addListener(_listener);
if (initState != null) initState(state);
},
dispose: (GetBuilderState<T> state) {
final consumerState = (state as GetConsumerState<T>);
VoidCallback _listener = _getConsumerListeners.getListener(
consumerState.consumerListenerKey,
);
init.removeListener(_listener);
_getConsumerListeners.removeListener(consumerState.consumerListenerKey);
if (dispose != null) dispose(state);
},
);
@override
GetBuilderState<T> createState() => GetConsumerState<T>();
}
class GetConsumerState<T extends GetxController> extends GetBuilderState<T> {
late final Key consumerListenerKey;
void generateConsumerListenerKey() {
consumerListenerKey = UniqueKey();
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'get_consumer.dart';
void main() {
runApp(MyWidget());
}
class MyController extends GetxController {
String text = 'Hola';
updateText(String str) {
text = str;
update();
}
}
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: GetConsumer<MyController>(
init: MyController(),
listener: (controller) {
print('Text was updated!');
print(controller.text);
},
builder: (MyController controller) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(controller.text),
ElevatedButton(
onPressed: () {
controller.updateText('New text');
},
child: Text('Change text'),
),
],
),
);
},
),
),
);
}
}
i tried to use it but couldnt make it work, can you look at what i did wrong?
Widget build(BuildContext context) { return GetConsumer<AuthController>( init: AuthController(), listener: (authController) { print('hello'); if (authController.registrationEventHandler == RegistrationEvent.inProcess) { openSnackbar( title: 'Attempting Registration', text: 'Please hold on...'); } }, builder: (AuthController tempController) { return GetX<AuthController>( builder: (_) { return SafeArea( child: Scaffold( backgroundColor: CustomColors.backGroundColor, body: Padding( padding: EdgeInsets.symmetric( horizontal: Design.horizontalPadding, ), child: Form( key: _signupKey, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ CustomText( alignment: TextAlign.left, text: "Register New Account", color: CustomColors.textWhiteColor, fontWeight: FontWeight.w600, fontSize: 36.sp, maxLines: 2, ), SizedBox( height: (24).h, ), CustomTextField( title: "Full Name", controller: fullNameController, validator: CustomValidators().fullNameValidator, keyboardType: TextInputType.name, onTap: () {}, prefixIcon: Icons.account_circle_outlined, ), SizedBox( height: (24).h, ), CustomTextField( title: "Email", controller: emailController, validator: CustomValidators().emailValidator, keyboardType: TextInputType.emailAddress, onTap: () {}, prefixIcon: Icons.email_outlined, ), SizedBox( height: (24).h, ), CustomTextField( hasFormatter: true, title: "Date of Birth", controller: dateOfBirthController, validator: CustomValidators().fieldValidator, keyboardType: TextInputType.number, onTap: () {}, prefixIcon: Icons.date_range, ), CustomTextField( title: "Contact Number", controller: contactNumberController, validator: CustomValidators().fieldValidator, keyboardType: TextInputType.number, onTap: () {}, prefixIcon: Icons.phone_enabled_outlined), SizedBox( height: (24).h, ), Column( children: [ CustomTextField( title: "Password", controller: passwordController, validator: CustomValidators().passwordValidator, isPassword: true, onTap: () { _changePasswordVisibility(); }, obscureText: _authController.isPasswordVisible.value, prefixIcon: Icons.password_outlined, ), SizedBox( height: (24).h, ), CustomTextField( title: "Confirm Password", controller: confirmPasswordController, validator: CustomValidators().passwordValidator, isPassword: true, onTap: () { _changePasswordVisibility(); }, obscureText: _authController.isPasswordVisible.value, prefixIcon: Icons.password_outlined, ), ], ), SizedBox( height: (65).h, ), CustomButton( text: 'Continue', onTap: () { tempController.registrationEventHandler = RegistrationEvent.inProcess; // _authController.attemptRegistration.value = // true; // if (_signupKey.currentState!.validate()) { // _getRegistrationData( // contactNumber: contactNumberController.text, // dateOfBirth: dateOfBirthController.text, // email: emailController.text, // fullName: fullNameController.text, // password: passwordController.text, // ); // Get.to(() => const SelectTeamScreen()); // } }), ], ), ), ), ), ), ); }); }); }
Hope my code can solve your problem GetxListener