Skip to content

Instantly share code, notes, and snippets.

@Jerome-Jumah
Last active July 25, 2024 08:21
Show Gist options
  • Save Jerome-Jumah/25fe77e603b5a86b1182f9977965b6e2 to your computer and use it in GitHub Desktop.
Save Jerome-Jumah/25fe77e603b5a86b1182f9977965b6e2 to your computer and use it in GitHub Desktop.
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
abstract class BaseState {}
class InitialState implements BaseState {}
class LoadingState implements BaseState {}
class LoadedState<T> implements BaseState {
final T data;
LoadedState(this.data);
}
class ErrorState implements BaseState {
final String message;
ErrorState(this.message);
}
abstract class DataHandler<T> {
T transformData(dynamic rawData);
}
abstract class DataSource {
Future<dynamic> fetchData();
}
class GenericViewModel<T> extends Cubit<BaseState> {
final DataHandler<T> dataHandler;
final DataSource? dataSource;
GenericViewModel({
required this.dataHandler,
required this.dataSource,
}) : super(InitialState());
Future<void> fetchData() async {
try {
emit(LoadingState());
final rawData = await dataSource?.fetchData();
final transformedData = dataHandler.transformData(rawData);
emit(LoadedState<T>(transformedData));
} catch (e) {
emit(ErrorState(e.toString()));
}
}
void updateData(T newData) {
emit(LoadedState<T>(newData));
}
// instead of accepting data of type T it should also accept any other data
void performAction<U>(T Function(T? data, [U? params]) doSomething, T data,
[U? params]) {
emit(LoadedState<T>(doSomething(data, params)));
}
}
class UserData {
final String name;
final int age;
UserData({required this.name, required this.age});
}
class UserDataHandler implements DataHandler<UserData> {
@override
UserData transformData(dynamic rawData) {
return UserData(
name: rawData['name'],
age: rawData['age'],
);
}
}
class UserDataSource implements DataSource {
@override
Future<dynamic> fetchData() async {
// Simulating API call
await Future.delayed(Duration(seconds: 2));
return {'name': 'John Doe', 'age': 30};
}
}
class CounterHandler implements DataHandler<int> {
@override
int transformData(dynamic rawData) {
return rawData as int;
}
}
// Usage
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => GenericViewModel<int>(
dataSource: null,
dataHandler: CounterHandler(),
),
),
BlocProvider(
create: (_) => GenericViewModel<UserData>(
dataHandler: UserDataHandler(),
dataSource: UserDataSource(),
)..fetchData(),
)
],
child: Scaffold(
body: UserWidget(),
floatingActionButton: GenericBlocConsumer<int>(
params: CounterConsumerArgs(),
),
),
),
);
}
}
class UserWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: GenericBlocConsumer<UserData>(
params: UserConsumerArgs(),
),
),
SizedBox(height: 4),
BlocBuilder<GenericViewModel<int>, BaseState>(
builder: (context, state) {
var value = state is LoadedState ? state.data : 0;
return ElevatedButton(
onPressed: () {
int add2(int? initialData, [int? x]) {
return (initialData ?? 0) + x!;
}
context
.read<GenericViewModel<int>>()
.performAction<int>(add2, value, 2);
},
child: Text('Add two more'),
);
}),
],
);
}
}
class GenericBlocConsumer<T> extends StatelessWidget {
final CustomBlocConsumerParams params;
const GenericBlocConsumer({
Key? key,
required this.params,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocConsumer<GenericViewModel<T>, BaseState>(
listener: (context, state) {
params.listener(context, state);
},
builder: (context, state) {
if (state is InitialState) {
return params.buildWhenInitial(context);
} else if (state is LoadingState) {
return params.buildWhenLoading(context);
} else if (state is LoadedState<T>) {
return params.buildWhenLoaded(context, state);
} else if (state is ErrorState) {
return params.buildWhenError(context, state.message);
} else {
return params.buildWhenOther(context, state);
}
},
);
}
}
abstract class CustomBlocConsumerParams<T> {
Widget buildWhenLoaded(BuildContext ctx, LoadedState<T> state);
Widget buildWhenInitial(BuildContext ctx);
Widget buildWhenLoading(BuildContext ctx);
Widget buildWhenError(BuildContext ctx, String msg);
Widget buildWhenOther(BuildContext ctx, BaseState state);
listener(BuildContext ctx, BaseState state);
}
class UserConsumerArgs
with ConsumerDeafaultStates
implements CustomBlocConsumerParams<UserData> {
@override
Widget buildWhenLoaded(context, LoadedState<UserData> state) {
return Text('User: ${state.data.name}, Age: ${state.data.age}');
}
@override
Widget buildWhenError(BuildContext ctx, String msg) {
return errorWidget(msg);
}
@override
Widget buildWhenInitial(BuildContext ctx) {
return initialWidget();
}
@override
Widget buildWhenLoading(BuildContext ctx) {
return loadingWidget();
}
@override
Widget buildWhenOther(BuildContext ctx, BaseState state) {
return unknownStateWidget();
}
@override
listener(BuildContext ctx, BaseState state) {}
}
class CounterConsumerArgs
with ConsumerDeafaultStates
implements CustomBlocConsumerParams {
@override
Widget buildWhenLoaded(context, state) {
var val = state.data;
return FloatingActionButton.extended(
label: Text('$val'),
onPressed: () {
var value = val;
value += 1;
context.read<GenericViewModel<int>>().updateData(value);
},
icon: Icon(Icons.add),
);
}
@override
Widget buildWhenError(BuildContext ctx, String msg) {
return errorWidget(msg);
}
@override
Widget buildWhenInitial(BuildContext ctx) {
var val = 0;
return FloatingActionButton.extended(
label: Text('$val'),
onPressed: () {
var value = val;
value += 1;
ctx.read<GenericViewModel<int>>().updateData(value);
},
icon: Icon(Icons.add),
);
}
@override
Widget buildWhenLoading(BuildContext ctx) {
return loadingWidget();
}
@override
Widget buildWhenOther(BuildContext ctx, BaseState state) {
return unknownStateWidget();
}
@override
listener(BuildContext ctx, BaseState state) {
if (state is LoadedState) {
ScaffoldMessenger.of(ctx).showSnackBar(
SnackBar(
content: const Text('Added'),
),
);
}
}
}
mixin ConsumerDeafaultStates {
Widget loadingWidget() {
return const Center(
child: CircularProgressIndicator(),
);
}
Widget initialWidget() {
return const Center(
child: Text('Initializing...'),
);
}
Widget errorWidget(String message) {
return Center(
child: Text(message),
);
}
Widget unknownStateWidget() {
return Center(
child: Text('Unknown State'),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment