main
md 210 lines 4.94 KB
Rendered Raw
1 # Handwritten Feature Example
2
3 Use this as a compact reference for new handwritten Riverpod code. Adapt it to the project's repository, error, pagination, and serialization conventions.
4
5 ## Contents
6
7 - [Model](#model)
8 - [Repository](#repository)
9 - [Providers](#providers)
10 - [Page](#page)
11 - [Components](#components)
12
13 ## Model
14
15 ```dart
16 // features/orders/data/models/order_model.dart
17 class OrderModel {
18 const OrderModel({
19 required this.id,
20 required this.customerName,
21 required this.total,
22 });
23
24 final String id;
25 final String customerName;
26 final double total;
27
28 factory OrderModel.fromJson(Map<String, dynamic> json) {
29 return OrderModel(
30 id: json['id'] as String? ?? '',
31 customerName: json['customerName'] as String? ?? '',
32 total: (json['total'] as num?)?.toDouble() ?? 0,
33 );
34 }
35
36 Map<String, dynamic> toJson() {
37 return {
38 'id': id,
39 'customerName': customerName,
40 'total': total,
41 };
42 }
43
44 OrderModel copyWith({
45 String? id,
46 String? customerName,
47 double? total,
48 }) {
49 return OrderModel(
50 id: id ?? this.id,
51 customerName: customerName ?? this.customerName,
52 total: total ?? this.total,
53 );
54 }
55
56 @override
57 bool operator ==(Object other) {
58 return identical(this, other) ||
59 other is OrderModel &&
60 other.id == id &&
61 other.customerName == customerName &&
62 other.total == total;
63 }
64
65 @override
66 int get hashCode => Object.hash(id, customerName, total);
67 }
68 ```
69
70 ## Repository
71
72 ```dart
73 // features/orders/data/repositories/orders_repository.dart
74 class OrdersRepository {
75 const OrdersRepository(this._client);
76
77 final ApiClient _client;
78
79 Future<List<OrderModel>> fetchOrders({String query = ''}) async {
80 final response = await _client.get('/orders', query: {'q': query});
81 return (response as List)
82 .map((json) => OrderModel.fromJson(json as Map<String, dynamic>))
83 .toList();
84 }
85
86 Future<void> deleteOrder(String id) {
87 return _client.delete('/orders/$id');
88 }
89 }
90 ```
91
92 ## Providers
93
94 ```dart
95 // features/orders/providers/orders_providers.dart
96 final ordersRepositoryProvider = Provider<OrdersRepository>((ref) {
97 final client = ref.watch(apiClientProvider);
98 return OrdersRepository(client);
99 });
100
101 final orderSearchQueryProvider = StateProvider.autoDispose<String>((ref) => '');
102
103 final ordersProvider =
104 AsyncNotifierProvider.autoDispose<OrdersNotifier, List<OrderModel>>(
105 OrdersNotifier.new,
106 );
107
108 class OrdersNotifier extends AutoDisposeAsyncNotifier<List<OrderModel>> {
109 @override
110 Future<List<OrderModel>> build() {
111 final repository = ref.watch(ordersRepositoryProvider);
112 final query = ref.watch(orderSearchQueryProvider);
113 return repository.fetchOrders(query: query);
114 }
115
116 Future<void> deleteOrder(String id) async {
117 state = const AsyncLoading<List<OrderModel>>();
118 state = await AsyncValue.guard(() async {
119 final repository = ref.read(ordersRepositoryProvider);
120 await repository.deleteOrder(id);
121 final query = ref.read(orderSearchQueryProvider);
122 return repository.fetchOrders(query: query);
123 });
124 }
125 }
126 ```
127
128 ## Page
129
130 ```dart
131 // features/orders/views/orders_page.dart
132 class OrdersPage extends ConsumerWidget {
133 const OrdersPage({super.key});
134
135 @override
136 Widget build(BuildContext context, WidgetRef ref) {
137 final orders = ref.watch(ordersProvider);
138
139 return orders.when(
140 loading: () => const Center(child: CircularProgressIndicator()),
141 error: (error, stackTrace) => ErrorView(error: error),
142 data: (items) => OrdersList(
143 orders: items,
144 onDelete: (id) => ref.read(ordersProvider.notifier).deleteOrder(id),
145 ),
146 );
147 }
148 }
149 ```
150
151 ## Components
152
153 ```dart
154 // features/orders/views/components/orders_list.dart
155 class OrdersList extends StatelessWidget {
156 const OrdersList({
157 super.key,
158 required this.orders,
159 required this.onDelete,
160 });
161
162 final List<OrderModel> orders;
163 final ValueChanged<String> onDelete;
164
165 @override
166 Widget build(BuildContext context) {
167 if (orders.isEmpty) {
168 return const Center(child: Text('No orders found'));
169 }
170
171 return ListView.separated(
172 itemCount: orders.length,
173 separatorBuilder: (context, index) => const Divider(height: 1),
174 itemBuilder: (context, index) {
175 final order = orders[index];
176 return OrderListTile(
177 order: order,
178 onDelete: () => onDelete(order.id),
179 );
180 },
181 );
182 }
183 }
184 ```
185
186 ```dart
187 // features/orders/views/components/order_list_tile.dart
188 class OrderListTile extends StatelessWidget {
189 const OrderListTile({
190 super.key,
191 required this.order,
192 required this.onDelete,
193 });
194
195 final OrderModel order;
196 final VoidCallback onDelete;
197
198 @override
199 Widget build(BuildContext context) {
200 return ListTile(
201 title: Text(order.customerName),
202 subtitle: Text('\$${order.total.toStringAsFixed(2)}'),
203 trailing: IconButton(
204 icon: const Icon(Icons.delete_outline),
205 onPressed: onDelete,
206 ),
207 );
208 }
209 }
210 ```