From 12568b3ada150343f341c5d31765a8a1f156f26b Mon Sep 17 00:00:00 2001 From: Hugo Pointcheval Date: Tue, 14 Nov 2023 14:42:16 +0100 Subject: [PATCH] feat(crud)!: make crudbuilder integration more easy by integrating blocbuilder (closes #181) --- .../example/lib/advanced_cubit_view.dart | 156 ++++++++---------- .../example/lib/basic_cubit_view.dart | 135 +++++++-------- .../example/lib/streaming_cubit_view.dart | 110 ++++++------ .../features/crud/builder/crud_builder.dart | 148 ++++++++++++----- 4 files changed, 294 insertions(+), 255 deletions(-) diff --git a/packages/wyatt_crud_bloc/example/lib/advanced_cubit_view.dart b/packages/wyatt_crud_bloc/example/lib/advanced_cubit_view.dart index 9671663e..f6bc82fd 100644 --- a/packages/wyatt_crud_bloc/example/lib/advanced_cubit_view.dart +++ b/packages/wyatt_crud_bloc/example/lib/advanced_cubit_view.dart @@ -35,91 +35,81 @@ class AdvancedCubitView extends StatelessWidget { create: (context) => UserAdvancedCubit(context.read>()) ..streaming(), - child: Builder(builder: (context) { - return Column( - children: [ - BlocBuilder( - buildWhen: (previous, current) { - if (current is CrudLoading && current is! CrudReading) { - return false; - } - return true; - }, - builder: (context, state) { - return Expanded( - child: CrudBuilder.typed>( - state: state, - builder: ((context, state) { - return ListView.builder( - itemCount: state.data.length, - itemBuilder: (context, index) { - final user = state.data.elementAt(index); - return ListTile( - title: Text(user?.name ?? 'Error'), - subtitle: Text(user?.email ?? 'Error'), - onTap: () { - context - .read() - .delete((user?.id)!); - }, - onLongPress: () { - context.read().update( - UpdateParameters( - id: user?.id ?? '', - raw: { - 'email': '${user?.id}@updated.io', - }), - ); - }, - ); - }, - ); - }), - initialBuilder: (context, state) => - const Center(child: CircularProgressIndicator()), - loadingBuilder: (context, state) => - const Center(child: CircularProgressIndicator()), - errorBuilder: (context, state) => Text("Error: $state"), - ), - ); - }, - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: () { - final r = Random().nextInt(1000); - context.read().create( - User( - id: '$r', - name: 'Wyatt $r', - email: '$r@wyattapp.io', - phone: '06$r', - ), + child: Builder( + builder: (context) { + return Column( + children: [ + Expanded( + child: + CrudBuilder.typed>( + buildWhen: (previous, current) { + if (current is CrudLoading && current is! CrudReading) { + return false; + } + return true; + }, + builder: ((context, state) { + return ListView.builder( + itemCount: state.data.length, + itemBuilder: (context, index) { + final user = state.data.elementAt(index); + return ListTile( + title: Text(user?.name ?? 'Error'), + subtitle: Text(user?.email ?? 'Error'), + onTap: () { + context.read().delete((user?.id)!); + }, + onLongPress: () { + context.read().update( + UpdateParameters(id: user?.id ?? '', raw: { + 'email': '${user?.id}@updated.io', + }), + ); + }, + ); + }, ); - }, - child: BlocBuilder( - buildWhen: (previous, current) { - if (current is CrudLoading && current is! CrudCreating) { - return false; - } - return true; - }, - builder: (context, state) { - return state is CrudCreating - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - color: Colors.white, - ), - ) - : const Text("Create"); - }, + }), + initialBuilder: (context, state) => + const Center(child: CircularProgressIndicator()), + loadingBuilder: (context, state) => + const Center(child: CircularProgressIndicator()), + errorBuilder: (context, state) => Text("Error: $state"), + ), ), - ), - ], - ); - }), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () { + final r = Random().nextInt(1000); + context.read().create( + User( + id: '$r', + name: 'Wyatt $r', + email: '$r@wyattapp.io', + phone: '06$r', + ), + ); + }, + child: CrudBuilder.onLoading( + builder: (context, state) { + if (state is CrudCreating) { + return const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + ), + ); + } + return null; + }, + otherBuilder: (context, state) => const Text("Create"), + ), + ), + ], + ); + } + ), ), ); } diff --git a/packages/wyatt_crud_bloc/example/lib/basic_cubit_view.dart b/packages/wyatt_crud_bloc/example/lib/basic_cubit_view.dart index 5f2ee88b..cb9759af 100644 --- a/packages/wyatt_crud_bloc/example/lib/basic_cubit_view.dart +++ b/packages/wyatt_crud_bloc/example/lib/basic_cubit_view.dart @@ -37,51 +37,44 @@ class BasicCubitView extends StatelessWidget { child: Builder(builder: (context) { return Column( children: [ - BlocBuilder( - buildWhen: (previous, current) { - if (current is CrudLoading && current is! CrudReading) { - return false; - } - return true; - }, - builder: (context, state) { - return Expanded( - child: CrudBuilder.typed>( - state: state, - builder: ((context, state) { - return ListView.builder( - itemCount: state.data.length, - itemBuilder: (context, index) { - final user = state.data.elementAt(index); - return ListTile( - title: Text(user?.name ?? 'Error'), - subtitle: Text(user?.email ?? 'Error'), - onTap: () { - context - .read() - .delete(id: (user?.id)!); - }, - onLongPress: () { - context.read().update( - single: UpdateParameters( - id: user?.id ?? '', - raw: { - 'email': '${user?.id}@updated.io', - }), - ); - }, - ); + Expanded( + child: CrudBuilder.typed>( + buildWhen: (previous, current) { + if (current is CrudLoading && current is! CrudReading) { + return false; + } + return true; + }, + builder: ((context, state) { + return ListView.builder( + itemCount: state.data.length, + itemBuilder: (context, index) { + final user = state.data.elementAt(index); + return ListTile( + title: Text(user?.name ?? 'Error'), + subtitle: Text(user?.email ?? 'Error'), + onTap: () { + context.read().delete(id: (user?.id)!); + }, + onLongPress: () { + context.read().update( + single: UpdateParameters( + id: user?.id ?? '', + raw: { + 'email': '${user?.id}@updated.io', + }), + ); }, ); - }), - initialBuilder: (context, state) => - const Center(child: CircularProgressIndicator()), - loadingBuilder: (context, state) => - const Center(child: CircularProgressIndicator()), - errorBuilder: (context, state) => Text("Error: $state"), - ), - ); - }, + }, + ); + }), + initialBuilder: (context, state) => + const Center(child: CircularProgressIndicator()), + loadingBuilder: (context, state) => + const Center(child: CircularProgressIndicator()), + errorBuilder: (context, state) => Text("Error: $state"), + ), ), const SizedBox(height: 20), ElevatedButton( @@ -96,48 +89,40 @@ class BasicCubitView extends StatelessWidget { ), ); }, - child: BlocBuilder( - buildWhen: (previous, current) { - if (current is CrudLoading && current is! CrudCreating) { - return false; - } - return true; - }, + child: CrudBuilder.onLoading( builder: (context, state) { - return state is CrudCreating - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - color: Colors.white, - ), - ) - : const Text("Create"); + if (state is CrudCreating) { + return const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + ), + ); + } + return null; }, + otherBuilder: (context, state) => const Text("Create"), ), ), ElevatedButton( onPressed: () { context.read().read(); }, - child: BlocBuilder( - buildWhen: (previous, current) { - if (current is CrudLoading && current is! CrudReading) { - return false; - } - return true; - }, + child: CrudBuilder.onLoading( builder: (context, state) { - return state is CrudReading - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - color: Colors.white, - ), - ) - : const Text("Read"); + if (state is CrudReading) { + return const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + ), + ); + } + return null; }, + otherBuilder: (context, state) => const Text("Read"), ), ), const SizedBox(height: 20), diff --git a/packages/wyatt_crud_bloc/example/lib/streaming_cubit_view.dart b/packages/wyatt_crud_bloc/example/lib/streaming_cubit_view.dart index 8f1df40d..e6f93d92 100644 --- a/packages/wyatt_crud_bloc/example/lib/streaming_cubit_view.dart +++ b/packages/wyatt_crud_bloc/example/lib/streaming_cubit_view.dart @@ -37,51 +37,47 @@ class StreamingCubitView extends StatelessWidget { child: Builder(builder: (context) { return Column( children: [ - BlocBuilder( - buildWhen: (previous, current) { - if (current is CrudLoading && current is! CrudReading) { - return false; - } - return true; - }, - builder: (context, state) { - return Expanded( - child: CrudBuilder.typed>( - state: state, - builder: ((context, state) { - return ListView.builder( - itemCount: state.data.length, - itemBuilder: (context, index) { - final user = state.data.elementAt(index); - return ListTile( - title: Text(user?.name ?? 'Error'), - subtitle: Text(user?.email ?? 'Error'), - onTap: () { - context - .read() - .delete(id: (user?.id)!); - }, - onLongPress: () { - context.read().update( - single: UpdateParameters( - id: user?.id ?? '', - raw: { - 'email': '${user?.id}@updated.io', - }), - ); - }, - ); + Expanded( + child: CrudBuilder.typed>( + buildWhen: (previous, current) { + if (current is CrudLoading && current is! CrudReading) { + return false; + } + return true; + }, + builder: ((context, state) { + return ListView.builder( + itemCount: state.data.length, + itemBuilder: (context, index) { + final user = state.data.elementAt(index); + return ListTile( + title: Text(user?.name ?? 'Error'), + subtitle: Text(user?.email ?? 'Error'), + onTap: () { + context + .read() + .delete(id: (user?.id)!); + }, + onLongPress: () { + context.read().update( + single: UpdateParameters( + id: user?.id ?? '', + raw: { + 'email': '${user?.id}@updated.io', + }), + ); }, ); - }), - initialBuilder: (context, state) => - const Center(child: CircularProgressIndicator()), - loadingBuilder: (context, state) => - const Center(child: CircularProgressIndicator()), - errorBuilder: (context, state) => Text("Error: $state"), - ), - ); - }, + }, + ); + }), + initialBuilder: (context, state) => + const Center(child: CircularProgressIndicator()), + loadingBuilder: (context, state) => + const Center(child: CircularProgressIndicator()), + errorBuilder: (context, state) => Text("Error: $state"), + ), ), const SizedBox(height: 20), ElevatedButton( @@ -96,24 +92,20 @@ class StreamingCubitView extends StatelessWidget { ), ); }, - child: BlocBuilder( - buildWhen: (previous, current) { - if (current is CrudLoading && current is! CrudCreating) { - return false; - } - return true; - }, + child: CrudBuilder.onLoading( builder: (context, state) { - return state is CrudCreating - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - color: Colors.white, - ), - ) - : const Text("Create"); + if (state is CrudCreating) { + return const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + ), + ); + } + return null; }, + otherBuilder: (context, state) => const Text("Create"), ), ), ], diff --git a/packages/wyatt_crud_bloc/lib/src/features/crud/builder/crud_builder.dart b/packages/wyatt_crud_bloc/lib/src/features/crud/builder/crud_builder.dart index d864ce7a..5789d599 100644 --- a/packages/wyatt_crud_bloc/lib/src/features/crud/builder/crud_builder.dart +++ b/packages/wyatt_crud_bloc/lib/src/features/crud/builder/crud_builder.dart @@ -15,26 +15,27 @@ // along with this program. If not, see . import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:wyatt_crud_bloc/src/features/crud/blocs/crud_base_cubit/crud_base_cubit.dart'; /// {@template crud_builder} /// A widget that builds itself based on the latest snapshot of interaction /// with a [CrudBaseCubit]. -/// -/// * I = Initial State -/// * L = Loading State -/// * S = Success State -/// * E = Error State /// {@endtemplate} -class CrudBuilder extends StatelessWidget { +class CrudBuilder< + Bloc extends StateStreamable, + Initial extends CrudState, + Loading extends CrudState, + Success extends CrudState, + Error extends CrudState> extends StatelessWidget { /// {@macro crud_builder} const CrudBuilder({ - required this.state, required this.builder, required this.initialBuilder, required this.loadingBuilder, required this.errorBuilder, this.unknownBuilder, + this.buildWhen, super.key, }); @@ -43,50 +44,121 @@ class CrudBuilder extends StatelessWidget { /// This factory constructor is used to create a [CrudBuilder] with /// [CrudState]s. `S` is the Success State, and it must be a subtype of /// [CrudSuccess]. It the only type that you have to specify. - static CrudBuilder - typed({ - required CrudState state, - required Widget Function(BuildContext, S) builder, + static CrudBuilder + typed, + Success extends CrudSuccess>({ + required Widget Function(BuildContext, Success) builder, required Widget Function(BuildContext, CrudInitial) initialBuilder, required Widget Function(BuildContext, CrudLoading) loadingBuilder, required Widget Function(BuildContext, CrudError) errorBuilder, - Widget Function(BuildContext, Object)? unknownBuilder, + Widget Function(BuildContext, CrudState)? unknownBuilder, + BlocBuilderCondition? buildWhen, }) => - CrudBuilder( - state: state, + CrudBuilder( builder: builder, initialBuilder: initialBuilder, loadingBuilder: loadingBuilder, errorBuilder: errorBuilder, unknownBuilder: unknownBuilder, + buildWhen: buildWhen, ); - final Object state; - final Widget Function(BuildContext context, S state) builder; - final Widget Function(BuildContext context, I state) initialBuilder; - final Widget Function(BuildContext context, L state) loadingBuilder; - final Widget Function(BuildContext context, E state) errorBuilder; - final Widget Function(BuildContext context, Object state)? unknownBuilder; + /// {@macro crud_builder} + /// + /// This factory constructor is used to create a [CrudBuilder] wich reacts + /// only to [CrudLoading] states. + static CrudBuilder + onLoading>({ + required Widget? Function(BuildContext, CrudLoading) builder, + Widget Function(BuildContext, CrudState)? otherBuilder, + BlocBuilderCondition? buildWhen, + }) => + CrudBuilder( + loadingBuilder: (context, state) => + builder.call(context, state) ?? + otherBuilder?.call(context, state) ?? + const SizedBox.shrink(), + builder: (context, state) => + otherBuilder?.call(context, state) ?? const SizedBox.shrink(), + initialBuilder: (context, state) => + otherBuilder?.call(context, state) ?? const SizedBox.shrink(), + errorBuilder: (context, state) => + otherBuilder?.call(context, state) ?? const SizedBox.shrink(), + unknownBuilder: otherBuilder, + buildWhen: buildWhen, + ); + + /// {@macro crud_builder} + /// + /// This factory constructor is used to create a [CrudBuilder] wich reacts + /// only to [CrudInitial] states. + static CrudBuilder + onInitial>({ + required Widget? Function(BuildContext, CrudInitial) builder, + Widget Function(BuildContext, CrudState)? otherBuilder, + BlocBuilderCondition? buildWhen, + }) => + CrudBuilder( + initialBuilder: (context, state) => + builder.call(context, state) ?? + otherBuilder?.call(context, state) ?? + const SizedBox.shrink(), + builder: (context, state) => + otherBuilder?.call(context, state) ?? const SizedBox.shrink(), + loadingBuilder: (context, state) => + otherBuilder?.call(context, state) ?? const SizedBox.shrink(), + errorBuilder: (context, state) => + otherBuilder?.call(context, state) ?? const SizedBox.shrink(), + unknownBuilder: otherBuilder, + buildWhen: buildWhen, + ); + + /// {@macro crud_builder} + /// + /// This factory constructor is used to create a [CrudBuilder] wich reacts + /// only to [CrudError] states. + static CrudBuilder + onError>({ + required Widget? Function(BuildContext, CrudError) builder, + Widget Function(BuildContext, CrudState)? otherBuilder, + BlocBuilderCondition? buildWhen, + }) => + CrudBuilder( + errorBuilder: (context, state) => + builder.call(context, state) ?? + otherBuilder?.call(context, state) ?? + const SizedBox.shrink(), + builder: (context, state) => + otherBuilder?.call(context, state) ?? const SizedBox.shrink(), + loadingBuilder: (context, state) => + otherBuilder?.call(context, state) ?? const SizedBox.shrink(), + initialBuilder: (context, state) => + otherBuilder?.call(context, state) ?? const SizedBox.shrink(), + unknownBuilder: otherBuilder, + buildWhen: buildWhen, + ); + + final BlocBuilderCondition? buildWhen; + final Widget Function(BuildContext context, Success state) builder; + final Widget Function(BuildContext context, Initial state) initialBuilder; + final Widget Function(BuildContext context, Loading state) loadingBuilder; + final Widget Function(BuildContext context, Error state) errorBuilder; + final Widget Function(BuildContext context, CrudState state)? unknownBuilder; @override - Widget build(BuildContext context) => Builder( - builder: (context) { - if (state is S) { - return builder(context, state as S); - } else if (state is E) { - return errorBuilder(context, state as E); - } else if (state is L) { - return loadingBuilder(context, state as L); - } else if (state is I) { - return initialBuilder(context, state as I); - } else { - return unknownBuilder?.call(context, state) ?? - Center( - child: Text( - 'Unknown state: $state', - ), - ); - } + Widget build(BuildContext context) => BlocBuilder( + buildWhen: buildWhen, + builder: (context, state) => switch (state) { + Success() => builder(context, state), + Initial() => initialBuilder(context, state), + Loading() => loadingBuilder(context, state), + Error() => errorBuilder(context, state), + _ => unknownBuilder?.call(context, state) ?? + Center( + child: Text( + 'Unknown state: $state', + ), + ), }, ); }