Skip to content

Instantly share code, notes, and snippets.

@hectorAguero
Created March 27, 2025 21:26
Show Gist options
  • Save hectorAguero/12bfcf2162dcafdce98a95d71f5ce13c to your computer and use it in GitHub Desktop.
Save hectorAguero/12bfcf2162dcafdce98a95d71f5ce13c to your computer and use it in GitHub Desktop.
Cubit + AsyncValue
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return RepositoryProvider(
create: (context) => ProductsRepository(),
child: BlocProvider(
create: (context) => ProductsCubit(context.read<ProductsRepository>()),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(body: MyPage()),
),
),
);
}
}
class MyPage extends StatefulWidget {
const MyPage({super.key});
@override
State<MyPage> createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read<ProductsCubit>().loginAndLoad('USER', 'PASSWORD');
},
child: Text('Fetch Data', textAlign: TextAlign.center),
),
body: BlocBuilder<ProductsCubit, AsyncValue<ProductsState>>(
builder: (context, state) {
return Center(
child: AnimatedSwitcher(
duration: Durations.medium1,
child: switch (state) {
AsyncIdle() => Text('Press the button to fetch Data'),
AsyncData() => Column(
mainAxisSize: MainAxisSize.min,
spacing: 8,
children: [
Text(state.data.items.toString()),
OutlinedButton(
child: Text("Update Data"),
onPressed: () {
context.read<ProductsCubit>().addData();
},
),
],
),
AsyncLoading() => Column(
mainAxisSize: MainAxisSize.min,
spacing: 8,
children: [CircularProgressIndicator(), Text('Loading')],
),
AsyncError() => Text('Error'),
},
),
);
},
),
);
}
}
/// Repository Class
class ProductsRepository {
Future<String> login(String username, String password) async {
await Future.delayed(Duration(seconds: 1));
return 'fake-token-123';
}
Future<List<String>> fetchItems(String token, int page) async {
await Future.delayed(Duration(seconds: 1));
return List.generate(10, (i) => 'Item ${page * 10 + i}');
}
}
// State for the Cubit,
class ProductsState {
final List<String> items;
ProductsState({required this.items});
@override
bool operator ==(covariant ProductsState other) {
return listEquals(this.items, other.items);
}
@override
int get hashCode => items.hashCode;
}
class ProductsCubit extends Cubit<AsyncValue<ProductsState>> {
final ProductsRepository repo;
String? _token;
ProductsCubit(this.repo) : super(AsyncValue.idle());
Future<void> loginAndLoad(String user, String pass) async {
try {
emit(AsyncLoading());
_token = await repo.login(user, pass);
final items = await repo.fetchItems(_token!, 0);
emit(AsyncData(ProductsState(items: items)));
} catch (e) {
emit(AsyncError(e.toString()));
}
}
void addData() {
if (!state.hasValue) return;
final item = 'Item ${state.value!.items.length}';
emit(AsyncData(ProductsState(items: [...state.value!.items, item])));
}
}
extension AsyncValueX<T> on AsyncValue<T> {
T? get valueOrNull {
if (hasValue) return value;
return null;
}
}
@immutable
sealed class AsyncValue<T> {
const AsyncValue();
factory AsyncValue.idle() = AsyncIdle<T>;
factory AsyncValue.data(T data) = AsyncData<T>;
factory AsyncValue.loading() = AsyncLoading<T>;
factory AsyncValue.error(Object error, [StackTrace? stackTrace]) =
AsyncError<T>;
bool get hasValue;
T? get value;
}
final class AsyncIdle<T> extends AsyncValue<T> {
const AsyncIdle();
bool get hasValue => false;
@override
T? get value => null;
}
final class AsyncData<T> extends AsyncValue<T> {
final T data;
const AsyncData(this.data);
@override
bool get hasValue => true;
@override
T? get value => data;
}
final class AsyncLoading<T> extends AsyncValue<T> {
const AsyncLoading();
bool get hasValue => false;
@override
T? get value => null;
}
class AsyncError<T> extends AsyncValue<T> {
final Object error;
final StackTrace? stackTrace;
const AsyncError(this.error, [this.stackTrace]);
bool get hasValue => false;
@override
T? get value => null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment