feat(crud)!: make crudbuilder integration more easy by integrating blocbuilder (closes #181)
Some checks failed
continuous-integration/drone/pr Build is failing

This commit is contained in:
Hugo Pointcheval 2023-11-14 14:42:16 +01:00
parent 3a7b7dfd1d
commit 12568b3ada
Signed by: hugo
GPG Key ID: 3AAC487E131E00BC
4 changed files with 294 additions and 255 deletions

View File

@ -35,91 +35,81 @@ class AdvancedCubitView extends StatelessWidget {
create: (context) =>
UserAdvancedCubit(context.read<CrudRepository<User>>())
..streaming(),
child: Builder(builder: (context) {
return Column(
children: [
BlocBuilder<UserAdvancedCubit, CrudState>(
buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudReading) {
return false;
}
return true;
},
builder: (context, state) {
return Expanded(
child: CrudBuilder.typed<CrudListLoaded<User?>>(
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<UserAdvancedCubit>()
.delete((user?.id)!);
},
onLongPress: () {
context.read<UserAdvancedCubit>().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<UserAdvancedCubit>().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<UserAdvancedCubit, CrudListLoaded<User?>>(
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<UserAdvancedCubit>().delete((user?.id)!);
},
onLongPress: () {
context.read<UserAdvancedCubit>().update(
UpdateParameters(id: user?.id ?? '', raw: {
'email': '${user?.id}@updated.io',
}),
);
},
);
},
);
},
child: BlocBuilder<UserAdvancedCubit, CrudState>(
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<UserAdvancedCubit>().create(
User(
id: '$r',
name: 'Wyatt $r',
email: '$r@wyattapp.io',
phone: '06$r',
),
);
},
child: CrudBuilder.onLoading<UserAdvancedCubit>(
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"),
),
),
],
);
}
),
),
);
}

View File

@ -37,51 +37,44 @@ class BasicCubitView extends StatelessWidget {
child: Builder(builder: (context) {
return Column(
children: [
BlocBuilder<UserCubit, CrudState>(
buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudReading) {
return false;
}
return true;
},
builder: (context, state) {
return Expanded(
child: CrudBuilder.typed<CrudListLoaded<User?>>(
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<UserCubit>()
.delete(id: (user?.id)!);
},
onLongPress: () {
context.read<UserCubit>().update(
single: UpdateParameters(
id: user?.id ?? '',
raw: {
'email': '${user?.id}@updated.io',
}),
);
},
);
Expanded(
child: CrudBuilder.typed<UserCubit, CrudListLoaded<User?>>(
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<UserCubit>().delete(id: (user?.id)!);
},
onLongPress: () {
context.read<UserCubit>().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<UserCubit, CrudState>(
buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudCreating) {
return false;
}
return true;
},
child: CrudBuilder.onLoading<UserCubit>(
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<UserCubit>().read();
},
child: BlocBuilder<UserCubit, CrudState>(
buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudReading) {
return false;
}
return true;
},
child: CrudBuilder.onLoading<UserCubit>(
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),

View File

@ -37,51 +37,47 @@ class StreamingCubitView extends StatelessWidget {
child: Builder(builder: (context) {
return Column(
children: [
BlocBuilder<UserStreamingCubit, CrudState>(
buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudReading) {
return false;
}
return true;
},
builder: (context, state) {
return Expanded(
child: CrudBuilder.typed<CrudListLoaded<User?>>(
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<UserStreamingCubit>()
.delete(id: (user?.id)!);
},
onLongPress: () {
context.read<UserStreamingCubit>().update(
single: UpdateParameters(
id: user?.id ?? '',
raw: {
'email': '${user?.id}@updated.io',
}),
);
},
);
Expanded(
child: CrudBuilder.typed<UserStreamingCubit,
CrudListLoaded<User?>>(
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<UserStreamingCubit>()
.delete(id: (user?.id)!);
},
onLongPress: () {
context.read<UserStreamingCubit>().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<UserStreamingCubit, CrudState>(
buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudCreating) {
return false;
}
return true;
},
child: CrudBuilder.onLoading<UserStreamingCubit>(
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"),
),
),
],

View File

@ -15,26 +15,27 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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<I, L, S, E> extends StatelessWidget {
class CrudBuilder<
Bloc extends StateStreamable<CrudState>,
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<I, L, S, E> 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<CrudInitial, CrudLoading, CrudSuccess, CrudError>
typed<S extends CrudSuccess>({
required CrudState state,
required Widget Function(BuildContext, S) builder,
static CrudBuilder<Bloc, CrudInitial, CrudLoading, CrudSuccess, CrudError>
typed<Bloc extends StateStreamable<CrudState>,
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<CrudState>? buildWhen,
}) =>
CrudBuilder<CrudInitial, CrudLoading, S, CrudError>(
state: state,
CrudBuilder<Bloc, CrudInitial, CrudLoading, Success, CrudError>(
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<Bloc, CrudInitial, CrudLoading, CrudSuccess, CrudError>
onLoading<Bloc extends StateStreamable<CrudState>>({
required Widget? Function(BuildContext, CrudLoading) builder,
Widget Function(BuildContext, CrudState)? otherBuilder,
BlocBuilderCondition<CrudState>? buildWhen,
}) =>
CrudBuilder<Bloc, CrudInitial, CrudLoading, CrudSuccess, CrudError>(
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<Bloc, CrudInitial, CrudLoading, CrudSuccess, CrudError>
onInitial<Bloc extends StateStreamable<CrudState>>({
required Widget? Function(BuildContext, CrudInitial) builder,
Widget Function(BuildContext, CrudState)? otherBuilder,
BlocBuilderCondition<CrudState>? buildWhen,
}) =>
CrudBuilder<Bloc, CrudInitial, CrudLoading, CrudSuccess, CrudError>(
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<Bloc, CrudInitial, CrudLoading, CrudSuccess, CrudError>
onError<Bloc extends StateStreamable<CrudState>>({
required Widget? Function(BuildContext, CrudError) builder,
Widget Function(BuildContext, CrudState)? otherBuilder,
BlocBuilderCondition<CrudState>? buildWhen,
}) =>
CrudBuilder<Bloc, CrudInitial, CrudLoading, CrudSuccess, CrudError>(
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<CrudState>? 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<Bloc, CrudState>(
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',
),
),
},
);
}