Created
March 27, 2025 21:26
-
-
Save hectorAguero/12bfcf2162dcafdce98a95d71f5ce13c to your computer and use it in GitHub Desktop.
Cubit + AsyncValue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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