getx_exten 2.0.3 copy "getx_exten: ^2.0.3" to clipboard
getx_exten: ^2.0.3 copied to clipboard

A package with GetListener and GetConsumer for Getx Lovers

GetX Extensions ๐Ÿš€ #

pub package License: MIT

A powerful state management extension for GetX that combines BLoC patterns with GetX's reactive superpowers. Build scalable Flutter apps with ease!

โœจ Features #

  • ๐ŸŽฏ Type Flexibility - Use ANY type as state (int, String, custom classes, etc.)
  • ๐Ÿ”ฅ GetX Reactive - Direct Rx support for lightweight reactive state
  • ๐Ÿ—๏ธ BLoC Patterns - Familiar Cubit and BLoC architecture
  • โšก Performance - Fine-grained reactivity with selectors
  • ๐ŸŽจ Rich Widgets - Specialized widgets for every use case
  • ๐Ÿงช Well Tested - Comprehensive test suite
  • ๐Ÿ“ฆ Zero Boilerplate - No base classes required

๐Ÿ“ฆ Installation #

Add to your pubspec.yaml:

dependencies:
  getx_exten: ^2.0.3
  get: ^4.6.5

๐ŸŽฏ Quick Start #

1. Create a Cubit (Simple State) #

class CounterCubit extends RxCubit<int> {
  CounterCubit() : super(0);
  
  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);
}

2. Use in Your Widget #

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final cubit = Get.put(CounterCubit());
    
    return Scaffold(
      body: Center(
        child: GetChanger<int>(
          controller: cubit,
          builder: (context, count) => Text('Count: $count'),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: cubit.increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

That's it! ๐ŸŽ‰

๐Ÿ“š Core Concepts #

RxCubit - Simple State Management #

Perfect for straightforward state that doesn't need events.

// Works with primitives
class CounterCubit extends RxCubit<int> {
  CounterCubit() : super(0);
  void increment() => emit(state + 1);
}

// Works with custom classes
class User {
  final String name;
  final int age;
  User(this.name, this.age);
}

class UserCubit extends RxCubit<User> {
  UserCubit() : super(User('', 0));
  
  void updateUser(String name, int age) {
    emit(User(name, age));
  }
}

// Works with nullable types
class SearchCubit extends RxCubit<String?> {
  SearchCubit() : super(null);
  
  void search(String query) => emit(query);
  void clear() => emit(null);
}

// Works with collections
class TodosCubit extends RxCubit<List<String>> {
  TodosCubit() : super([]);
  
  void addTodo(String todo) => emit([...state, todo]);
}

RxBloc - Event-Driven State Management #

For complex state logic with events.

// Define events
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
class AddValue extends CounterEvent {
  final int value;
  AddValue(this.value);
}

// Create bloc
class CounterBloc extends RxBloc<CounterEvent, int> {
  CounterBloc() : super(0) {
    on<Increment>((event, emit) async {
      emit(state + 1);
    });
    
    on<Decrement>((event, emit) async {
      emit(state - 1);
    });
    
    on<AddValue>((event, emit) async {
      emit(state + event.value);
    });
  }
}

// Use in widget
final bloc = Get.put(CounterBloc());
bloc.add(Increment());
bloc.add(AddValue(5));

๐ŸŽจ Widgets #

GetChanger - Simple Builder #

Rebuilds when state changes. Perfect for displaying state.

GetChanger<int>(
  controller: counterCubit,
  builder: (context, count) => Text('$count'),
)

// With buildWhen condition
GetChanger<int>(
  controller: counterCubit,
  buildWhen: (prev, curr) => curr % 2 == 0, // Only rebuild on even numbers
  builder: (context, count) => Text('$count'),
)

// With direct Rx
final count = 0.obs;
GetChanger<int>(
  rx: count,
  builder: (context, value) => Text('$value'),
)

GetListenerWidget - Side Effects Only #

Listens to state without rebuilding. Perfect for navigation, snackbars, dialogs.

GetListenerWidget<int>(
  controller: counterCubit,
  listener: (context, count) {
    if (count > 10) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Count exceeded 10!')),
      );
    }
  },
  child: MyWidget(),
)

// With listenWhen condition
GetListenerWidget<int>(
  controller: counterCubit,
  listenWhen: (prev, curr) => curr > prev, // Only listen on increase
  listener: (context, count) {
    print('Count increased: $count');
  },
  child: MyWidget(),
)

GetConsumer - Builder + Listener #

Combines building and listening. Perfect for complex UI with side effects.

GetConsumer<int>(
  controller: counterCubit,
  listener: (context, count) {
    // Side effects
    if (count == 10) {
      showDialog(context: context, builder: (_) => AlertDialog(...));
    }
  },
  builder: (context, count) {
    // UI
    return Text('$count');
  },
)

// With independent conditions
GetConsumer<int>(
  controller: counterCubit,
  listenWhen: (prev, curr) => curr % 5 == 0, // Listen every 5
  buildWhen: (prev, curr) => curr % 2 == 0,  // Build every 2
  listener: (context, count) => print('Multiple of 5: $count'),
  builder: (context, count) => Text('$count'),
)

GetSelector - Fine-Grained Reactivity #

Only rebuilds when the selected value changes. HUGE performance boost!

class UserState {
  final String name;
  final int age;
  final List<String> hobbies;
  
  UserState(this.name, this.age, this.hobbies);
}

class UserCubit extends RxCubit<UserState> {
  UserCubit() : super(UserState('', 0, []));
  
  void updateName(String name) => emit(UserState(name, state.age, state.hobbies));
  void updateAge(int age) => emit(UserState(state.name, age, state.hobbies));
}

// Only rebuilds when NAME changes (not age or hobbies!)
GetSelector<UserState, String>(
  controller: userCubit,
  selector: (state) => state.name,
  builder: (context, name) => Text('Name: $name'),
)

// Using extension method (cleaner syntax)
userCubit.select(
  (state) => state.age,
  (context, age) => Text('Age: $age'),
)

// With primitive selectors
GetSelector<int, bool>(
  controller: counterCubit,
  selector: (count) => count > 10,
  builder: (context, isHigh) => Text(isHigh ? 'High' : 'Low'),
)

GetMultiChanger - Multiple Sources #

Reacts to multiple state sources. Perfect for combining different cubits.

GetMultiChanger(
  sources: [
    Get.find<CartCubit>().rx,
    Get.find<PriceCubit>().rx,
  ],
  builder: (context) {
    final cart = Get.find<CartCubit>();
    final price = Get.find<PriceCubit>();
    
    return Text('${cart.state.length} items - \$${price.state}');
  },
)

// With mixed sources
final userCubit = Get.find<UserCubit>();
final count = 0.obs;

GetMultiChanger(
  sources: [userCubit.rx, count],
  builder: (context) => Text('${userCubit.state.name}: ${count.value}'),
)

๐ŸŽ“ Advanced Usage #

Custom State Classes #

No inheritance required! Define your state however you want.

// Simple class
class TodoState {
  final List<Todo> todos;
  final bool isLoading;
  
  TodoState(this.todos, this.isLoading);
}

// Sealed classes (recommended for complex states)
sealed class AuthState {}
class Authenticated extends AuthState {
  final User user;
  Authenticated(this.user);
}
class Unauthenticated extends AuthState {}
class AuthLoading extends AuthState {}

class AuthCubit extends RxCubit<AuthState> {
  AuthCubit() : super(Unauthenticated());
  
  Future<void> login(String email, String password) async {
    emit(AuthLoading());
    try {
      final user = await authService.login(email, password);
      emit(Authenticated(user));
    } catch (e) {
      emit(Unauthenticated());
    }
  }
}

Pattern Matching with Sealed Classes #

GetChanger<AuthState>(
  controller: authCubit,
  builder: (context, state) {
    return switch (state) {
      Authenticated(:final user) => HomePage(user: user),
      Unauthenticated() => LoginPage(),
      AuthLoading() => LoadingPage(),
    };
  },
)

Watching State Imperatively #

class MyController extends GetxController {
  final counterCubit = CounterCubit();
  late Worker _worker;
  
  @override
  void onInit() {
    super.onInit();
    
    // Watch state changes
    _worker = counterCubit.watch((count) {
      print('Count changed to: $count');
      
      if (count == 10) {
        // Do something
      }
    });
  }
  
  @override
  void onClose() {
    _worker.dispose();
    super.onClose();
  }
}

Combining Multiple Conditions #

GetConsumer<TodoState>(
  controller: todoCubit,
  // Rebuild only when todos list changes
  buildWhen: (prev, curr) => prev.todos.length != curr.todos.length,
  // Listen only when loading state changes
  listenWhen: (prev, curr) => prev.isLoading != curr.isLoading,
  listener: (context, state) {
    if (!state.isLoading) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Todos loaded!')),
      );
    }
  },
  builder: (context, state) {
    if (state.isLoading) return CircularProgressIndicator();
    return ListView.builder(
      itemCount: state.todos.length,
      itemBuilder: (context, index) => TodoItem(state.todos[index]),
    );
  },
)

๐ŸŽฏ Best Practices #

1. Use the Right Widget for the Job #

// โœ… Good - Use GetChanger for simple display
GetChanger<int>(
  controller: cubit,
  builder: (context, count) => Text('$count'),
)

// โœ… Good - Use GetListenerWidget for side effects
GetListenerWidget<String>(
  controller: messageCubit,
  listener: (context, message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  },
  child: MyWidget(),
)

// โŒ Bad - Don't use GetConsumer when you only need building
GetConsumer<int>(
  controller: cubit,
  listener: (context, count) {}, // Empty listener
  builder: (context, count) => Text('$count'),
)

2. Leverage Selectors for Performance #

// โŒ Bad - Rebuilds when ANY field changes
GetChanger<UserState>(
  controller: userCubit,
  builder: (context, state) => Text(state.name),
)

// โœ… Good - Only rebuilds when name changes
GetSelector<UserState, String>(
  controller: userCubit,
  selector: (state) => state.name,
  builder: (context, name) => Text(name),
)

3. Use Direct Rx for Simple Cases #

// For simple reactive values, skip the cubit
class MyController extends GetxController {
  final count = 0.obs;
  final name = 'John'.obs;
  final isLoading = false.obs;
  
  void increment() => count.value++;
}

// Use directly
GetChanger<int>(
  rx: controller.count,
  builder: (context, count) => Text('$count'),
)

4. Combine with GetX Dependency Injection #

// In your binding or main.dart
Get.put(CounterCubit());
Get.lazyPut(() => UserCubit());

// Access anywhere
final counterCubit = Get.find<CounterCubit>();

5. Proper Disposal #

class MyPage extends StatefulWidget {
  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  late final CounterCubit cubit;
  
  @override
  void initState() {
    super.initState();
    cubit = CounterCubit();
  }
  
  @override
  void dispose() {
    cubit.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return GetChanger<int>(
      controller: cubit,
      builder: (context, count) => Text('$count'),
    );
  }
}

๐Ÿงช Testing #

All widgets are fully testable:

testWidgets('GetChanger rebuilds on state change', (tester) async {
  final cubit = CounterCubit();
  
  await tester.pumpWidget(
    MaterialApp(
      home: GetChanger<int>(
        controller: cubit,
        builder: (context, count) => Text('$count'),
      ),
    ),
  );
  
  expect(find.text('0'), findsOneWidget);
  
  cubit.increment();
  await tester.pump();
  
  expect(find.text('1'), findsOneWidget);
});

๐Ÿ“Š Comparison with Other Solutions #

Feature GetX Extensions flutter_bloc GetX Alone
Type Flexibility โœ… Any type โœ… Any type โœ… Any type
Fine-grained Selectors โœ… Built-in โœ… Via BlocSelector โŒ Manual
Multi-source Reactivity โœ… GetMultiChanger โŒ Manual โœ… Obx
Performance โšก Excellent โšก Excellent โšก Excellent
Boilerplate ๐ŸŸข Low ๐ŸŸก Medium ๐ŸŸข Low
Learning Curve ๐ŸŸข Easy ๐ŸŸก Medium ๐ŸŸข Easy
BLoC Pattern โœ… Optional โœ… Core โŒ Not built-in
Direct Rx Support โœ… Yes โŒ No โœ… Yes

๐Ÿค Contributing #

Contributions are welcome! Please read our contributing guidelines first.

๐Ÿ“„ License #

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ™ Acknowledgments #

  • Built on top of the amazing GetX package
  • Inspired by flutter_bloc
  • Thanks to all contributors!

๐Ÿ“ž Support #


Made with โค๏ธ by the GetX Extensions team

3
likes
150
points
20
downloads

Publisher

unverified uploader

Weekly Downloads

A package with GetListener and GetConsumer for Getx Lovers

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, get

More

Packages that depend on getx_exten