i try to program a small app to track my cost spendings. As state management I use Cubit. As I am a flutter beginner, I wanted to verify if I implemented it correctly.
The UI consists of 2 screens: 1 for the cost entry, 1 for the listing of the entered expenses. Concerning architecture, I followed the cubit guidelines: UI - Cubit - Repository. The repository only consists of a list and the data is represented by cost-objects.
The classes interact like:
- UI calls Method in the Cubit class
- The Cubit class emits a state, which holds data
- Depending on the state the data from the cubit are used in the UI (state...)
Some questions and ideas are included as comments
main.dart
import 'package:easy_cost_splitter/screens/CostPage.dart';
import 'package:easy_cost_splitter/screens/ListPage.dart';
import 'package:flutter/material.dart';
import 'package:easy_cost_splitter/repositories/FakeLocalRepository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'logic/cubit/manage_cost_cubit.dart';
void main() {
runApp(MainApp());
}
class MainApp extends StatelessWidget {
MainApp({super.key});
final fakeLocalRepository = FakeLocalRepository();
@override
Widget build(BuildContext context) {
return BlocProvider( // I PUT IT ON TOP OF THE APP, IN ORDER TO HAVE ACCESS FROM ALL SCREENS
create: (context) => ManageCostCubit(fakeLocalRepository),
child: Builder(builder: (context) {
return MaterialApp(
routes: {
"/CostPage": (context) => const CostPage(),
"/ListPage": (context) => const ListPage(),
},
initialRoute: "/CostPage",
theme: ThemeData(
primarySwatch: Colors.amber,
),
home: const ListPage(),
);
}),
);
}
}
Class CostList:
import 'package:easy_cost_splitter/logic/cubit/manage_cost_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../data/models/cost.dart';
class ListPage extends StatefulWidget {
const ListPage({super.key});
@override
State<ListPage> createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
late List<Cost> costList;
@override
void initState() {
super.initState();
final costCubit = context.read<ManageCostCubit>();
// I PUT THE CONNECTION OF THE WIDGET TO THE CUBIT IN THE INITSTATE-METHOD. WHEN I PUT IT INSIDE THE BUILD METHOD, IT UPDATED EVERY FRAME
costCubit.getAllCost();
// WHEN BUILDING THE SCREEN INITIALLY, TO GET A COST-LIST. I AM NOT SURE IF THIS IS RIGHT HERE.
}
@override
Widget build(BuildContext context) {
return BlocBuilder<ManageCostCubit, ManageCostState>(
builder: (context, state) {
if (state is ManageCostSuccess) {
costList = state.costs;
} else {
costList = [];
}
return Scaffold(
appBar: AppBar(
title: const Text("Easy Cost Splitter"),
),
body: SafeArea(
child: Center(
child: state is ManageCostLoading
? CircularProgressIndicator()
// THE CICULARPROGRESSINDICATOR ONLY WORKS HERE CORRECTLY, WHEN I PUT IT ABOVE THE SCAFFOLD AND PUT IT IN A CONSTRAINED BOX, IT STILL COVERS THE WHOLE SCREEN
: Column(
children: [
ListView.builder(
padding: const EdgeInsets.all(20),
itemCount:
state is ManageCostSuccess ? state.costs.length : 0,
// WHY IS IT NOT POSSIBLE TO MAKE A COMPLEX "IF () ELSE ..." REQUEST HERE???
scrollDirection: Axis
.vertical,
shrinkWrap:
true,
itemBuilder: ((context, index) {
return ListTile(
title: Text(costList[index].item),
subtitle: Text(costList[index].cost.toString()),
onTap: () {
// I CAN NOT CALL THE "costCubit." OBJECT FROM THE INIT-STATE METHOD, WHY??
},
);
}),
),
const SizedBox(
height: 40,
),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, "/CostPage");
},
child: const Text('Seite Wechseln')),
],
),
),
),
);
});
}
}
Class Cubit
import "package:easy_cost_splitter/repositories/FakeLocalRepository.dart";
import "package:equatable/equatable.dart";
import "package:flutter_bloc/flutter_bloc.dart";
import "package:easy_cost_splitter/data/models/cost.dart";
part "manage_cost_state.dart";
class ManageCostCubit extends Cubit<ManageCostState> {
FakeLocalRepository fakeLocalRepository;
// ManageCostCubit(this.fakeLocalRepository) : super(ManageCostInitial());
ManageCostCubit(this.fakeLocalRepository) : super(ManageCostInitial());
Future<void> getAllCost() async {
emit(ManageCostLoading());
await Future.delayed(Duration(seconds: 3));
try {
final costs = await fakeLocalRepository.getAllCost();
emit(ManageCostSuccess(costs: costs));
print("in ManageCostCubit.getAllCost: " + state.toString());
} on Exception {
emit(ManageCostError());
}
}
Future<void> addCost(String sIn, double iIn) async {
emit(ManageCostLoading());
Cost cIn = new Cost(sIn, iIn);
print(state.toString());
try {
fakeLocalRepository.addCost(cIn);
final costs = await fakeLocalRepository.getAllCost();
// IN ORDER TO BE ABLE TO EMIT THE SUCCESS-CLASS I NEED A LIST OF COSTS TO PASS IT TO THE CONSTRUCTOR; DON'T KNOW IF THIS IS GOOD STYLE?
emit(ManageCostSuccess(costs: costs));
print(state.toString());
print(costs.last.item);
} on Exception {
emit(ManageCostError());
}
}
}
Class State
part of "manage_cost_cubit.dart";
abstract class ManageCostState extends Equatable {
const ManageCostState();
@override
List<Object> get props => [];
}
class ManageCostInitial extends ManageCostState {}
class ManageCostLoading extends ManageCostState {}
class ManageCostSuccess extends ManageCostState {
final List<Cost> costs;
const ManageCostSuccess({required this.costs});
@override
List<Object> get props => [costs];
}
class ManageCostError extends ManageCostState {}