I'm working on refactoring an app and try to apply BLoC / Cubit architecture.
The code provided works fine, but I need your advice to improve this refactoring according to BLoC principles, before I start to refactor all the project this way. My main goals are to make it easy to developers to switch to this new architecture and as readable as possible.
original version spot_widget.dart
import 'package:project/models/latlon.dart';
import 'package:project/models/spot_info.dart';
import 'package:project/models/spot_review.dart';
import 'package:flutter/widgets.dart';
class SpotWidget extends StatefulWidget {
final LatLon location;
const SpotWidget({super.key, required this.location});
@override
State<SpotWidget> createState() => _SpotWidgetState();
}
class _SpotWidgetState extends State<SpotWidget> {
SpotInfo? info;
List<SpotReview> reviews = [];
Future<void> _loadInfo() async {
final infos = await SpotInfo.get(widget.location);
info = infos.first;
setState(() {});
}
Future<void> _loadReviews() async {
reviews = await SpotReview.get(widget.location);
setState(() {});
}
@override
void initState() {
_loadInfo();
_loadReviews();
super.initState();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(widget.location.toString()),
// TODO: Widget to display all informations about a spot
if (info != null) Text(info!.isPublic.toString()),
// TODO: Widget to display all reviews about a spot
if (reviews.isNotEmpty) Text(reviews.length.toString()),
],
);
}
}
BLoC refactoring
spot_repository.dart
import 'package:project/models/latlon.dart';
import 'package:project/models/spot_info.dart';
import 'package:project/models/spot_review.dart';
class SpotRepository {
Future<SpotInfo> getSpotInfo(LatLon location) async {
final infos = await SpotInfo.get(location);
return infos.first;
}
Future<List<SpotReview>> getSpotReviews(LatLon location) async {
return await SpotReview.get(location);
}
}
spot_cubit.dart
import 'package:project/models/latlon.dart';
import 'package:project/spot/spot_repository.dart';
import 'package:project/spot/spot_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class SpotCubit extends Cubit<SpotState> {
final SpotRepository _repository;
SpotCubit(this._repository) : super(SpotInitial());
Future<void> loadSpotInfoAndReviews(LatLon location) async {
emit(SpotLoading());
try {
final info = await _repository.getSpotInfo(location);
final reviews = await _repository.getSpotReviews(location);
emit(SpotLoaded(info, reviews));
} catch (e) {
emit(SpotError("Failed to load spot info and reviews"));
}
}
}
spot_state.dart
import 'package:project/models/spot_info.dart';
import 'package:project/models/spot_review.dart';
import 'package:flutter/material.dart';
@immutable
abstract class SpotState {}
class SpotInitial extends SpotState {}
class SpotLoading extends SpotState {}
class SpotLoaded extends SpotState {
final SpotInfo info;
final List<SpotReview> reviews;
SpotLoaded(this.info, this.reviews);
}
class SpotError extends SpotState {
final String message;
SpotError(this.message);
}
spot_widget.dart
import 'package:project/models/latlon.dart';
import 'package:project/spot/spot_cubit.dart';
import 'package:project/spot/spot_repository.dart';
import 'package:project/spot/spot_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class SpotBlocWidget extends StatelessWidget {
final LatLon location;
const SpotBlocWidget({super.key, required this.location});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
SpotCubit(SpotRepository())..loadSpotInfoAndReviews(location),
child: BlocBuilder<SpotCubit, SpotState>(
builder: (context, state) {
if (state is SpotLoading) {
return const CircularProgressIndicator();
} else if (state is SpotLoaded) {
return Column(
children: [
Text(location.toString()),
Text(state.info.isPublic.toString()),
Text(state.reviews.length.toString()),
],
);
} else if (state is SpotError) {
return Text(state.message);
} else {
return const Text('Something went wrong');
}
},
),
);
}
}
Everything looks good. But as per Bloc/Cubit there will be lot of boilerplate so it will be more scalable and easy to modify. In
spot_cubit.dartit is advised to separate both repository call into different functions, will be helpful to handle error and scale in the future like below. It will also be easy to change from cubit to Bloc when needed. As Spot has multiple callings, changing it into a bloc is advised but not a must, this depends on it need to be scales in the future. If pagination need to be added to the spots list, separating this now will help in adding pagination feature.In
spot_widget.darteach state's widget can extracted to separated method and placed in new file likeAnd Error widget to separate file so that it can be reused. In Bloc/Cubit, a lot of code means, it can scaled and reused with ease.