Skip to content

Instantly share code, notes, and snippets.

@lomza
Last active January 7, 2023 18:47
Show Gist options
  • Save lomza/9a44ce9431106e2160ff0cfb9e2c1da9 to your computer and use it in GitHub Desktop.
Save lomza/9a44ce9431106e2160ff0cfb9e2c1da9 to your computer and use it in GitHub Desktop.
/// Environments config with GetIt
final getIt = GetIt.instance;
const dev = Environment(AppEnv.devName);
const prod = Environment(AppEnv.prodName);
@InjectableInit(preferRelativeImports: false)
void configureDependencies(String appEnv) => $initGetIt(getIt, environment: appEnv);
class AppEnv {
AppEnv._(
this.name, {
required this.deeplinkUri,
});
factory AppEnv.development() => AppEnv._(
devName,
deeplinkUri: 'https://myappdev.page.link',
);
factory AppEnv.production() => AppEnv._(
prodName,
deeplinkUri: 'https://myapp.page.link',
);
static const devName = 'dev';
static const prodName = 'prod';
final String name;
final String deeplinkUri;
}
// in main_dev.dart
void main() => runMain(AppEnv.devName);
// in prod_dev.dart
void main() => runMain(AppEnv.prodName);
// in main.dart
configureDependencies(appEnv);
/// Cubit & Hooks extensions
T useCubit<T extends Cubit>([List<dynamic> keys = const <dynamic>[]]) {
// ignore: unnecessary_lambdas
final cubit = useMemoized(() => getIt<T>(), keys);
useEffect(() => cubit.close, [cubit]);
return cubit;
}
S useCubitBuilder<C extends Cubit, S>(
Cubit<S> cubit, {
bool Function(S current)? buildWhen,
}) {
final state = useMemoized(
() => cubit.stream.where(buildWhen ?? (state) => true),
[cubit.state],
);
return useStream(state, initialData: cubit.state).requireData!;
}
void useCubitListener<BLOC extends Cubit<S>, S>(
BLOC bloc,
BlocListener<BLOC, S> listener, {
bool Function(S current)? listenWhen,
}) {
final context = useContext();
final listenWhenConditioner = listenWhen;
useMemoized(
() {
final stream =
bloc.stream.where(listenWhenConditioner ?? (state) => true).listen((state) => listener(bloc, state, context));
return stream.cancel;
},
[bloc],
);
}
typedef BlocListener<BLOC extends Cubit<S>, S> = void Function(BLOC cubit, S current, BuildContext context);
/// Usage in widget:
class BookDetailsPage extends HookWidget {
BookDetailsPage({
Key? key,
this.book,
}) : super(key: key);
final Book? book;
final _bookFormKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
var currentBook = book;
final cubit = useCubit<BookDetailsCubit>();
final state = useCubitBuilder<BookDetailsCubit, BookDetailsPageState>(cubit);
final titleTextController = useTextEditingController(text: book?.title ?? '');
final authorTextController = useTextEditingController(text: book?.author ?? '');
final aboutTextController = useTextEditingController(text: book?.about ?? '');
useEffect(
() {
cubit.init(book);
return null;
},
[cubit],
);
useCubitListener<BookDetailsCubit, BookDetailsPageState>(cubit, (cubit, state, context) {
state.maybeWhen(
saveBook: (book) {
if (_bookFormKey.currentState!.validate()) {
Navigator.pop(context); // TODO return result back to list
}
},
deleteBook: (book) {
if (_bookFormKey.currentState!.validate()) {
Navigator.pop(context); // TODO return result back to list
}
},
orElse: () => null,
);
}, listenWhen: (state) => state is BookDetailsPageSaveBook);
return Scaffold(
appBar: AppBar(
title: Text(LocaleKeys.books_details_title_add.tr()),
actions: [
if (book != null)
IconButton(
icon: const Icon(Icons.delete_forever_rounded),
tooltip: LocaleKeys.books_details_delete.tr(),
onPressed: () => cubit.deleteBook(book!),
),
IconButton(
icon: const Icon(Icons.save_rounded),
tooltip: LocaleKeys.books_details_save.tr(),
onPressed: () {
if (_bookFormKey.currentState!.validate()) {
cubit.saveBook(currentBook!);
}
},
),
],
),
body: state.maybeWhen(
orElse: () => const SizedBox.shrink(),
loading: () => const Center(child: CircularProgressIndicator()),
loadBook: (book) => SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(AppDimensions.l),
child: Form(
key: _bookFormKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AppTextEdit(
labelText: LocaleKeys.books_details_book_title.tr(),
textInputType: TextInputType.name,
textEditingController: titleTextController,
validator: (value) => (value == null || value.isEmpty) ? '' : null,
),
const SizedBox(height: AppDimensions.l),
AppTextEdit(
labelText: LocaleKeys.books_details_author.tr(),
textEditingController: authorTextController,
textInputType: TextInputType.name,
validator: (value) => (value == null || value.isEmpty) ? '' : null,
),
const SizedBox(height: AppDimensions.l),
AppTextEdit(
labelText: LocaleKeys.books_details_about.tr(),
textInputType: TextInputType.multiline,
lines: 5,
textEditingController: aboutTextController,
validator: (value) => (value == null || value.isEmpty) ? '' : null,
),
],
),
),
),
),
),
);
}
}
@injectable
class BookDetailsCubit extends Cubit<BookDetailsPageState> {
final AddBookUseCase _addBookUseCase;
final DeleteBookUseCase _deleteBookUseCase;
BookDetailsCubit(
this._addBookUseCase,
this._deleteBookUseCase,
) : super(const BookDetailsPageState.loading());
void init(Book? book) async {
emit(BookDetailsPageState.loadBook(....));
});
}
void saveBook(Book book) {
emit(BookDetailsPageState.saveBook(book));
}
void deleteBook(Book book) {
emit(BookDetailsPageState.deleteBook(book));
}
}
part of 'book_details_cubit.dart';
@freezed
class BookDetailsPageState with _$BookDetailsPageState {
const factory BookDetailsPageState.loading() = _BookDetailsPageLoading;
const factory BookDetailsPageState.loadBook(Book? book) = _BookDetailsPageLoadBook;
const factory BookDetailsPageState.saveBook(Book book) = BookDetailsPageSaveBook;
const factory BookDetailsPageState.deleteBook(Book book) = _BookDetailsPageDeleteBook;
}
/// Riverpod extensions
import 'package:core/core.dart';
import 'package:flutter/material.dart';
extension StateNotifierX on ProviderListenable<AsyncValue> {
void listenForError(BuildContext context, WidgetRef ref, Function(Exception exception) buildErrorMessage) {
ref.listen<AsyncValue>(this, (previousState, currentState) {
currentState.whenOrNull(error: (error, _) {
if (error is Exception) {
context.showSnackBar(buildErrorMessage(error));
}
});
});
}
void listenForData<T>(WidgetRef ref, Function(T data) onData) {
ref.listen<AsyncValue>(this, (previousState, currentState) {
currentState.whenData((data) => onData(data));
});
}
}
exampleNotifier.listenForData<bool>(ref, (data) {
if (data) {
context.showSnackBar('Got data!');
}
});
/// Freezed model with a custom method
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
@freezed
class User with _$User {
const User._();
const factory User({
required String identifier,
required String name,
List<Role>? roles,
}) = _User;
bool hasWorkRole() => roles?.any((r) => r.type == RoleType.work) ?? false;
}
/// Make use of new enums !
enum RoleType {
home(0),
work(1),
unknown(2);
final int role;
const RoleType(this.role);
static RoleType toDomainModel(String role) =>
RoleType.values.firstWhere((e) => e.number == int.parse(role), orElse: () => RoleType.unknown);
}
/// Riverpod + Freezed
// profile_data_state.dart
part 'profile_data_state.freezed.dart';
@freezed
class ProfileDataState with _$ProfileDataState {
const factory ProfileDataState.loading() = Loading;
const factory ProfileDataState.success(User user) = Success;
const factory ProfileDataState.error(Exception error) = Error;
}
// profile_providers.dart
final getUserUseCaseProvider = Provider(
(ref) => GetUserUseCase(),
);
final profileDataStateProvider =
StateNotifierProvider.autoDispose<ProfileDataNotifier, ProfileDataState>((ref) {
return ProfileDataNotifier(ref.watch(getUserUseCaseProvider));
});
// profile_data_notifier.dart
class ProfileDataNotifier extends StateNotifier<ProfileDataState> {
final GetUserUseCase _getUserUseCase;
ContactFormDataNotifier(this._getUserUseCase) : super(const ProfileDataState.loading()) {
getProfileData();
}
Future<void> getProfileData() async {
state = const ProfileDataState.loading();
final result = await _getUserUseCase.execute();
result.onSuccess((data) {
state = ProfileDataState.success(data);
});
result.onFailure((error) {
state = ProfileDataState.error(error);
});
}
}
// get_user_use_case.dart
class GetUserUseCase {
Future<Result<bool>> execute() async {...};
}
// profile_data_screen.dart
class ProfileDataScreen extends ConsumerWidget {
@override
Widget build(BuildContext context) {
ProfileDataState state = ref.watch(profileDataStateProvider);
return Scaffold(
body: state.when(
loading: () => CircularProgressIndicator(),
success: (user) {
return ProfileDataForm(user);
},
error: (reason) => ErrorWidget(),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment