CRUD: move firestore implementation in his own package #231

Merged
hugo merged 9 commits from feat/crud-update into master 2023-11-14 13:52:27 +00:00
4 changed files with 294 additions and 255 deletions
Showing only changes of commit 12568b3ada - Show all commits

View File

@ -35,20 +35,19 @@ class AdvancedCubitView extends StatelessWidget {
create: (context) => create: (context) =>
UserAdvancedCubit(context.read<CrudRepository<User>>()) UserAdvancedCubit(context.read<CrudRepository<User>>())
..streaming(), ..streaming(),
child: Builder(builder: (context) { child: Builder(
builder: (context) {
return Column( return Column(
children: [ children: [
BlocBuilder<UserAdvancedCubit, CrudState>( Expanded(
child:
CrudBuilder.typed<UserAdvancedCubit, CrudListLoaded<User?>>(
buildWhen: (previous, current) { buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudReading) { if (current is CrudLoading && current is! CrudReading) {
return false; return false;
} }
return true; return true;
}, },
builder: (context, state) {
return Expanded(
child: CrudBuilder.typed<CrudListLoaded<User?>>(
state: state,
builder: ((context, state) { builder: ((context, state) {
return ListView.builder( return ListView.builder(
itemCount: state.data.length, itemCount: state.data.length,
@ -58,15 +57,11 @@ class AdvancedCubitView extends StatelessWidget {
title: Text(user?.name ?? 'Error'), title: Text(user?.name ?? 'Error'),
subtitle: Text(user?.email ?? 'Error'), subtitle: Text(user?.email ?? 'Error'),
onTap: () { onTap: () {
context context.read<UserAdvancedCubit>().delete((user?.id)!);
.read<UserAdvancedCubit>()
.delete((user?.id)!);
}, },
onLongPress: () { onLongPress: () {
context.read<UserAdvancedCubit>().update( context.read<UserAdvancedCubit>().update(
UpdateParameters( UpdateParameters(id: user?.id ?? '', raw: {
id: user?.id ?? '',
raw: {
'email': '${user?.id}@updated.io', 'email': '${user?.id}@updated.io',
}), }),
); );
@ -81,8 +76,6 @@ class AdvancedCubitView extends StatelessWidget {
const Center(child: CircularProgressIndicator()), const Center(child: CircularProgressIndicator()),
errorBuilder: (context, state) => Text("Error: $state"), errorBuilder: (context, state) => Text("Error: $state"),
), ),
);
},
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
ElevatedButton( ElevatedButton(
@ -97,29 +90,26 @@ class AdvancedCubitView extends StatelessWidget {
), ),
); );
}, },
child: BlocBuilder<UserAdvancedCubit, CrudState>( child: CrudBuilder.onLoading<UserAdvancedCubit>(
buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudCreating) {
return false;
}
return true;
},
builder: (context, state) { builder: (context, state) {
return state is CrudCreating if (state is CrudCreating) {
? const SizedBox( return const SizedBox(
width: 20, width: 20,
height: 20, height: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: Colors.white, color: Colors.white,
), ),
) );
: const Text("Create"); }
return null;
}, },
otherBuilder: (context, state) => const Text("Create"),
), ),
), ),
], ],
); );
}), }
),
), ),
); );
} }

View File

@ -37,17 +37,14 @@ class BasicCubitView extends StatelessWidget {
child: Builder(builder: (context) { child: Builder(builder: (context) {
return Column( return Column(
children: [ children: [
BlocBuilder<UserCubit, CrudState>( Expanded(
child: CrudBuilder.typed<UserCubit, CrudListLoaded<User?>>(
buildWhen: (previous, current) { buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudReading) { if (current is CrudLoading && current is! CrudReading) {
return false; return false;
} }
return true; return true;
}, },
builder: (context, state) {
return Expanded(
child: CrudBuilder.typed<CrudListLoaded<User?>>(
state: state,
builder: ((context, state) { builder: ((context, state) {
return ListView.builder( return ListView.builder(
itemCount: state.data.length, itemCount: state.data.length,
@ -57,9 +54,7 @@ class BasicCubitView extends StatelessWidget {
title: Text(user?.name ?? 'Error'), title: Text(user?.name ?? 'Error'),
subtitle: Text(user?.email ?? 'Error'), subtitle: Text(user?.email ?? 'Error'),
onTap: () { onTap: () {
context context.read<UserCubit>().delete(id: (user?.id)!);
.read<UserCubit>()
.delete(id: (user?.id)!);
}, },
onLongPress: () { onLongPress: () {
context.read<UserCubit>().update( context.read<UserCubit>().update(
@ -80,8 +75,6 @@ class BasicCubitView extends StatelessWidget {
const Center(child: CircularProgressIndicator()), const Center(child: CircularProgressIndicator()),
errorBuilder: (context, state) => Text("Error: $state"), errorBuilder: (context, state) => Text("Error: $state"),
), ),
);
},
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
ElevatedButton( ElevatedButton(
@ -96,48 +89,40 @@ class BasicCubitView extends StatelessWidget {
), ),
); );
}, },
child: BlocBuilder<UserCubit, CrudState>( child: CrudBuilder.onLoading<UserCubit>(
buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudCreating) {
return false;
}
return true;
},
builder: (context, state) { builder: (context, state) {
return state is CrudCreating if (state is CrudCreating) {
? const SizedBox( return const SizedBox(
width: 20, width: 20,
height: 20, height: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: Colors.white, color: Colors.white,
), ),
) );
: const Text("Create"); }
return null;
}, },
otherBuilder: (context, state) => const Text("Create"),
), ),
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
context.read<UserCubit>().read(); context.read<UserCubit>().read();
}, },
child: BlocBuilder<UserCubit, CrudState>( child: CrudBuilder.onLoading<UserCubit>(
buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudReading) {
return false;
}
return true;
},
builder: (context, state) { builder: (context, state) {
return state is CrudReading if (state is CrudReading) {
? const SizedBox( return const SizedBox(
width: 20, width: 20,
height: 20, height: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: Colors.white, color: Colors.white,
), ),
) );
: const Text("Read"); }
return null;
}, },
otherBuilder: (context, state) => const Text("Read"),
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),

View File

@ -37,17 +37,15 @@ class StreamingCubitView extends StatelessWidget {
child: Builder(builder: (context) { child: Builder(builder: (context) {
return Column( return Column(
children: [ children: [
BlocBuilder<UserStreamingCubit, CrudState>( Expanded(
child: CrudBuilder.typed<UserStreamingCubit,
CrudListLoaded<User?>>(
buildWhen: (previous, current) { buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudReading) { if (current is CrudLoading && current is! CrudReading) {
return false; return false;
} }
return true; return true;
}, },
builder: (context, state) {
return Expanded(
child: CrudBuilder.typed<CrudListLoaded<User?>>(
state: state,
builder: ((context, state) { builder: ((context, state) {
return ListView.builder( return ListView.builder(
itemCount: state.data.length, itemCount: state.data.length,
@ -80,8 +78,6 @@ class StreamingCubitView extends StatelessWidget {
const Center(child: CircularProgressIndicator()), const Center(child: CircularProgressIndicator()),
errorBuilder: (context, state) => Text("Error: $state"), errorBuilder: (context, state) => Text("Error: $state"),
), ),
);
},
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
ElevatedButton( ElevatedButton(
@ -96,24 +92,20 @@ class StreamingCubitView extends StatelessWidget {
), ),
); );
}, },
child: BlocBuilder<UserStreamingCubit, CrudState>( child: CrudBuilder.onLoading<UserStreamingCubit>(
buildWhen: (previous, current) {
if (current is CrudLoading && current is! CrudCreating) {
return false;
}
return true;
},
builder: (context, state) { builder: (context, state) {
return state is CrudCreating if (state is CrudCreating) {
? const SizedBox( return const SizedBox(
width: 20, width: 20,
height: 20, height: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: Colors.white, color: Colors.white,
), ),
) );
: const Text("Create"); }
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/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart'; 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'; import 'package:wyatt_crud_bloc/src/features/crud/blocs/crud_base_cubit/crud_base_cubit.dart';
/// {@template crud_builder} /// {@template crud_builder}
/// A widget that builds itself based on the latest snapshot of interaction /// A widget that builds itself based on the latest snapshot of interaction
/// with a [CrudBaseCubit]. /// with a [CrudBaseCubit].
///
/// * I = Initial State
/// * L = Loading State
/// * S = Success State
/// * E = Error State
/// {@endtemplate} /// {@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} /// {@macro crud_builder}
const CrudBuilder({ const CrudBuilder({
required this.state,
required this.builder, required this.builder,
required this.initialBuilder, required this.initialBuilder,
required this.loadingBuilder, required this.loadingBuilder,
required this.errorBuilder, required this.errorBuilder,
this.unknownBuilder, this.unknownBuilder,
this.buildWhen,
super.key, super.key,
}); });
@ -43,50 +44,121 @@ class CrudBuilder<I, L, S, E> extends StatelessWidget {
/// This factory constructor is used to create a [CrudBuilder] with /// This factory constructor is used to create a [CrudBuilder] with
/// [CrudState]s. `S` is the Success State, and it must be a subtype of /// [CrudState]s. `S` is the Success State, and it must be a subtype of
/// [CrudSuccess]. It the only type that you have to specify. /// [CrudSuccess]. It the only type that you have to specify.
static CrudBuilder<CrudInitial, CrudLoading, CrudSuccess, CrudError> static CrudBuilder<Bloc, CrudInitial, CrudLoading, CrudSuccess, CrudError>
typed<S extends CrudSuccess>({ typed<Bloc extends StateStreamable<CrudState>,
required CrudState state, Success extends CrudSuccess>({
required Widget Function(BuildContext, S) builder, required Widget Function(BuildContext, Success) builder,
required Widget Function(BuildContext, CrudInitial) initialBuilder, required Widget Function(BuildContext, CrudInitial) initialBuilder,
required Widget Function(BuildContext, CrudLoading) loadingBuilder, required Widget Function(BuildContext, CrudLoading) loadingBuilder,
required Widget Function(BuildContext, CrudError) errorBuilder, required Widget Function(BuildContext, CrudError) errorBuilder,
Widget Function(BuildContext, Object)? unknownBuilder, Widget Function(BuildContext, CrudState)? unknownBuilder,
BlocBuilderCondition<CrudState>? buildWhen,
}) => }) =>
CrudBuilder<CrudInitial, CrudLoading, S, CrudError>( CrudBuilder<Bloc, CrudInitial, CrudLoading, Success, CrudError>(
state: state,
builder: builder, builder: builder,
initialBuilder: initialBuilder, initialBuilder: initialBuilder,
loadingBuilder: loadingBuilder, loadingBuilder: loadingBuilder,
errorBuilder: errorBuilder, errorBuilder: errorBuilder,
unknownBuilder: unknownBuilder, unknownBuilder: unknownBuilder,
buildWhen: buildWhen,
); );
final Object state; /// {@macro crud_builder}
final Widget Function(BuildContext context, S state) builder; ///
final Widget Function(BuildContext context, I state) initialBuilder; /// This factory constructor is used to create a [CrudBuilder] wich reacts
final Widget Function(BuildContext context, L state) loadingBuilder; /// only to [CrudLoading] states.
final Widget Function(BuildContext context, E state) errorBuilder; static CrudBuilder<Bloc, CrudInitial, CrudLoading, CrudSuccess, CrudError>
final Widget Function(BuildContext context, Object state)? unknownBuilder; 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 @override
Widget build(BuildContext context) => Builder( Widget build(BuildContext context) => BlocBuilder<Bloc, CrudState>(
builder: (context) { buildWhen: buildWhen,
if (state is S) { builder: (context, state) => switch (state) {
return builder(context, state as S); Success() => builder(context, state),
} else if (state is E) { Initial() => initialBuilder(context, state),
return errorBuilder(context, state as E); Loading() => loadingBuilder(context, state),
} else if (state is L) { Error() => errorBuilder(context, state),
return loadingBuilder(context, state as L); _ => unknownBuilder?.call(context, state) ??
} else if (state is I) {
return initialBuilder(context, state as I);
} else {
return unknownBuilder?.call(context, state) ??
Center( Center(
child: Text( child: Text(
'Unknown state: $state', 'Unknown state: $state',
), ),
); ),
}
}, },
); );
} }