-
-
Notifications
You must be signed in to change notification settings - Fork 975
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs(rest_api): add example for REST Api handling #3957
base: master
Are you sure you want to change the base?
Conversation
WalkthroughThis pull request introduces a comprehensive example of a REST API Flutter application using Riverpod for state management and Dio for HTTP requests. The project includes a complete implementation with a user management interface, featuring user listing, adding new users, error handling, and a full test suite. The example demonstrates modern Dart and Flutter development practices, including asynchronous state management, form validation, and comprehensive testing strategies. Changes
Sequence DiagramsequenceDiagram
participant UI as UserListScreen
participant State as UsersNotifier
participant Repo as UserRepository
participant API as REST API
UI->>State: Fetch Users
State->>Repo: fetchUsers()
Repo->>API: GET /users
API-->>Repo: Return User List
Repo-->>State: Update Users
State-->>UI: Render User List
UI->>State: Add New User
State->>Repo: createUser(newUser)
Repo->>API: POST /users
API-->>Repo: Confirm User Creation
Repo-->>State: Update State
State-->>UI: Refresh User List
Poem
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (12)
examples/rest_api/analysis_options.yaml (1)
12-26
: Consider enabling additional linting rules for REST API code.For a REST API example, consider enabling these additional rules to enforce better practices:
always_specify_types
: Helps with API model claritysort_constructors_first
: Maintains consistent class organizationApply this diff to add these rules:
rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + always_specify_types: true + sort_constructors_first: trueexamples/rest_api/test/widget_test.dart (3)
11-45
: Enhance test coverage with more comprehensive test data and assertions.The test verifies basic functionality but could be more thorough.
Consider these improvements:
when(mockDio.get('https://jsonplaceholder.typicode.com/users')) .thenAnswer((_) async { await Future.delayed(const Duration(milliseconds: 100)); return Response( data: [ - {'id': 1, 'name': 'Test User', 'email': '[email protected]'} + {'id': 1, 'name': 'Test User', 'email': '[email protected]'}, + {'id': 2, 'name': 'Another User', 'email': '[email protected]'} ], statusCode: 200, requestOptions: RequestOptions(path: ''), ); }); // Act & Assert remains the same... // Add more assertions +expect(find.byType(ListTile), findsNWidgets(2)); +expect(find.text('Another User'), findsOneWidget); +expect(find.text('[email protected]'), findsOneWidget);
47-71
: Improve error handling test coverage.The error handling test could be more robust.
Consider these improvements:
// Act remains the same... // Assert +// Verify loading indicator is gone +expect(find.byType(CircularProgressIndicator), findsNothing); + +// Verify error message expect( find.text('Error: Exception: Failed to load users'), findsOneWidget); + +// Verify retry functionality +await tester.tap(find.byType(RefreshIndicator)); +await tester.pump(); +verify(mockDio.get('https://jsonplaceholder.typicode.com/users')).called(2);
73-106
: Enhance form testing coverage.The test could be more comprehensive in testing form validation and navigation.
Consider adding these test cases:
+// Test form validation +await tester.tap(find.byType(ElevatedButton)); +await tester.pumpAndSettle(); +expect(find.text('Please enter a name'), findsOneWidget); +expect(find.text('Please enter an email'), findsOneWidget); + +// Test invalid email +await tester.enterText(find.byType(TextFormField).first, 'New User'); +await tester.enterText(find.byType(TextFormField).last, 'invalid-email'); +await tester.tap(find.byType(ElevatedButton)); +await tester.pumpAndSettle(); +expect(find.text('Please enter a valid email'), findsOneWidget); + // Fill form with valid data await tester.enterText(find.byType(TextFormField).first, 'New User'); await tester.enterText( find.byType(TextFormField).last, '[email protected]'); await tester.tap(find.byType(ElevatedButton)); await tester.pumpAndSettle(); // Verify API call verify(mockDio.post( 'https://jsonplaceholder.typicode.com/users', data: anyNamed('data'), )).called(1); + +// Verify navigation +expect(find.byType(AddUserScreen), findsNothing);examples/rest_api/test/user_test.dart (2)
14-71
: Add test cases for error scenarios and request configurations.The tests cover basic functionality but could be more comprehensive.
Consider adding these test cases:
test('fetchUsers handles network error', () async { when(mockDio.get('https://jsonplaceholder.typicode.com/users')) .thenThrow(DioException( requestOptions: RequestOptions(path: ''), error: 'Network error', )); expect( () => repository.fetchUsers(), throwsA(isA<Exception>().having( (e) => e.toString(), 'message', 'Exception: Failed to load users', )), ); }); test('createUser handles validation error', () async { when(mockDio.post( 'https://jsonplaceholder.typicode.com/users', data: any, )).thenThrow(DioException( requestOptions: RequestOptions(path: ''), response: Response( data: {'error': 'Invalid data'}, statusCode: 400, requestOptions: RequestOptions(path: ''), ), )); expect( () => repository.createUser(User(id: 0, name: '', email: '')), throwsA(isA<Exception>()), ); }); test('requests include correct headers', () async { await repository.fetchUsers(); verify(mockDio.get( any, options: argThat( predicate<Options>((options) => options.headers?['Content-Type'] == 'application/json' ), named: 'options', ), )).called(1); });
73-152
: Enhance state management testing coverage.The tests could better verify state transitions and error handling.
Consider adding these test cases:
test('loading state is shown during fetch', () async { // Arrange when(mockDio.get('https://jsonplaceholder.typicode.com/users')) .thenAnswer((_) async { await Future.delayed(const Duration(milliseconds: 100)); return Response( data: [], statusCode: 200, requestOptions: RequestOptions(path: ''), ); }); // Act & Assert expect( container.read(usersProvider), const AsyncValue<List<User>>.loading(), ); }); test('error state is shown on fetch failure', () async { // Arrange when(mockDio.get('https://jsonplaceholder.typicode.com/users')) .thenThrow(Exception('Network error')); // Act await expectLater( container.read(usersProvider.future), throwsException, ); // Assert expect( container.read(usersProvider), isA<AsyncError>(), ); }); test('state is preserved during refresh', () async { // Arrange initial state when(mockDio.get('https://jsonplaceholder.typicode.com/users')) .thenAnswer((_) async => Response( data: [ { 'id': 1, 'name': 'Initial User', 'email': '[email protected]', } ], statusCode: 200, requestOptions: RequestOptions(path: ''), )); // Wait for initial load await container.read(usersProvider.future); // Verify initial state expect(container.read(usersProvider).value?.length, 1); // Mock refresh response when(mockDio.get('https://jsonplaceholder.typicode.com/users')) .thenAnswer((_) async => Response( data: [ { 'id': 1, 'name': 'Updated User', 'email': '[email protected]', } ], statusCode: 200, requestOptions: RequestOptions(path: ''), )); // Act: Refresh the state await container.refresh(usersProvider.future); // Assert: Verify state is updated final users = container.read(usersProvider).value; expect(users?.length, 1); expect(users?.first.name, 'Updated User'); });examples/rest_api/lib/main.dart (3)
21-41
: Enhance the User model with validation and utility methods.The model class could be more robust and developer-friendly.
Consider these improvements:
class User { final int id; final String name; final String email; User({required this.id, required this.name, required this.email}); // Factory constructor for JSON deserialization factory User.fromJson(Map<String, dynamic> json) { + // Validate required fields + if (json['id'] == null) throw FormatException('Missing id'); + if (json['name'] == null) throw FormatException('Missing name'); + if (json['email'] == null) throw FormatException('Missing email'); + return User( id: json['id'], name: json['name'], email: json['email'], ); } // Method for JSON serialization Map<String, dynamic> toJson() => { 'id': id, 'name': name, 'email': email, }; + + // Utility method for creating copies + User copyWith({ + int? id, + String? name, + String? email, + }) => + User( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + ); + + @override + String toString() => 'User(id: $id, name: $name, email: $email)'; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is User && + other.id == id && + other.name == name && + other.email == email; + + @override + int get hashCode => Object.hash(id, name, email); }
89-106
: Enhance state management with caching and optimistic updates.The state management could be more efficient and user-friendly.
Consider these improvements:
class UsersNotifier extends AsyncNotifier<List<User>> { + static const _cacheTimeout = Duration(minutes: 5); + DateTime? _lastFetch; + @override Future<List<User>> build() async { + _lastFetch = DateTime.now(); final repository = ref.watch(userRepositoryProvider); return repository.fetchUsers(); } Future<void> addUser(User user) async { final repository = ref.watch(userRepositoryProvider); - state = const AsyncValue.loading(); + // Optimistic update + final currentUsers = state.value ?? []; + state = AsyncValue.data([...currentUsers, user]); + + try { + final newUser = await repository.createUser(user); + // Update with actual data from server + state = AsyncValue.data([...currentUsers, newUser]); + } catch (e) { + // Revert on error + state = AsyncValue.data(currentUsers); + rethrow; + } + } + + Future<void> refreshIfNeeded() async { + if (_lastFetch == null || + DateTime.now().difference(_lastFetch!) > _cacheTimeout) { + await build(); + } } }
109-150
: Add search and pagination for better user experience.The list screen could be more functional and user-friendly.
Consider these improvements:
class UserListScreen extends ConsumerWidget { const UserListScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final usersAsync = ref.watch(usersProvider); + final searchController = TextEditingController(); return Scaffold( appBar: AppBar( - title: const Text('Users'), + title: TextField( + controller: searchController, + decoration: const InputDecoration( + hintText: 'Search users...', + border: InputBorder.none, + ), + onChanged: (value) { + // TODO: Implement search functionality + }, + ), ), floatingActionButton: FloatingActionButton( onPressed: () => Navigator.push( context, MaterialPageRoute(builder: (context) => const AddUserScreen()), ), child: const Icon(Icons.add), ), body: switch (usersAsync) { AsyncData(:final value) => RefreshIndicator( onRefresh: () => ref.refresh(usersProvider.future), child: ListView.builder( itemCount: value.length, + // Add pagination + onEndReached: () { + // TODO: Load more users + }, + onEndReachedThreshold: 0.8, itemBuilder: (context, index) { final user = value[index]; return ListTile( title: Text(user.name), subtitle: Text(user.email), + onTap: () { + // TODO: Show user details + }, ); }, ), ), - AsyncError(:final error) => Center(child: Text('Error: $error')), + AsyncError(:final error) => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Error: $error'), + ElevatedButton( + onPressed: () => ref.refresh(usersProvider), + child: const Text('Retry'), + ), + ], + ), + ), _ => const Center(child: CircularProgressIndicator()), }, ); } }examples/rest_api/pubspec.yaml (3)
1-4
: Remove trailing space on line 3.The
publish_to
line contains a trailing space that should be removed.Apply this diff to fix the formatting:
-publish_to: 'none' +publish_to: 'none'🧰 Tools
🪛 YAMLlint (1.35.1)
[error] 3-3: trailing spaces
(trailing-spaces)
8-14
: Remove unnecessary blank line between dependencies.The dependencies section looks good, with appropriate version constraints. However, there's an unnecessary blank line between
dio
andcupertino_icons
that should be removed for consistency.Apply this diff to fix the formatting:
dio: ^5.7.0 - cupertino_icons: ^1.0.8
24-27
: Remove consecutive blank lines in Flutter configuration.The Flutter configuration is correct, but there are unnecessary consecutive blank lines that should be removed for better readability.
Apply this diff to fix the formatting:
flutter: - uses-material-design: true
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
examples/rest_api/.metadata
(1 hunks)examples/rest_api/README.md
(1 hunks)examples/rest_api/analysis_options.yaml
(1 hunks)examples/rest_api/lib/main.dart
(1 hunks)examples/rest_api/pubspec.yaml
(1 hunks)examples/rest_api/test/user_test.dart
(1 hunks)examples/rest_api/test/user_test.mocks.dart
(1 hunks)examples/rest_api/test/widget_test.dart
(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- examples/rest_api/.metadata
- examples/rest_api/README.md
🧰 Additional context used
🪛 YAMLlint (1.35.1)
examples/rest_api/pubspec.yaml
[error] 3-3: trailing spaces
(trailing-spaces)
⏰ Context from checks skipped due to timeout of 90000ms (19)
- GitHub Check: build (master, packages/riverpod_generator/integration/build_yaml)
- GitHub Check: build (master, packages/riverpod_generator)
- GitHub Check: build (master, packages/riverpod_annotation)
- GitHub Check: build (master, packages/riverpod/example)
- GitHub Check: build (master, packages/riverpod)
- GitHub Check: build (stable, packages/hooks_riverpod/example)
- GitHub Check: build (stable, packages/flutter_riverpod/example)
- GitHub Check: build (stable, packages/flutter_riverpod)
- GitHub Check: riverpod_lint (stable, packages/riverpod_lint_flutter_test)
- GitHub Check: build (stable, examples/todos)
- GitHub Check: riverpod_lint (stable, packages/riverpod_analyzer_utils_tests)
- GitHub Check: build (stable, examples/stackoverflow)
- GitHub Check: build (stable, examples/random_number)
- GitHub Check: riverpod_lint (master, packages/riverpod_lint_flutter_test)
- GitHub Check: build (stable, examples/pub)
- GitHub Check: build (stable, examples/marvel)
- GitHub Check: riverpod_lint (master, packages/riverpod_analyzer_utils_tests)
- GitHub Check: build (stable, examples/counter)
- GitHub Check: check_generation
🔇 Additional comments (6)
examples/rest_api/analysis_options.yaml (3)
1-7
: LGTM! Clear and helpful documentation.The header provides comprehensive information about the analyzer's purpose and usage.
27-28
: LGTM! Helpful reference link provided.The link to additional documentation is valuable for users who need more information.
8-10
: Verify flutter_lints package dependency.The configuration includes flutter_lints, which is good practice. Let's verify that it's properly added to pubspec.yaml.
✅ Verification successful
✓ flutter_lints package is properly configured
The package is correctly added to pubspec.yaml with version ^5.0.0.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Check if flutter_lints is properly added to pubspec.yaml # Look for flutter_lints in pubspec.yaml rg -A 2 "flutter_lints:" "examples/rest_api/pubspec.yaml"Length of output: 120
examples/rest_api/test/user_test.mocks.dart (1)
1-1035
: LGTM!This is an auto-generated mock file by Mockito. The implementation looks correct and complete.
examples/rest_api/pubspec.yaml (2)
6-7
: LGTM!The SDK constraint is well-defined and follows best practices by allowing patch and minor updates while preventing major version updates that could introduce breaking changes.
16-22
: LGTM!The dev dependencies are well-chosen for a comprehensive testing setup, including tools for widget testing, mocking, code generation, and static analysis.
class AddUserScreen extends ConsumerStatefulWidget { | ||
const AddUserScreen({super.key}); | ||
|
||
@override | ||
AddUserScreenState createState() => AddUserScreenState(); | ||
} | ||
|
||
class AddUserScreenState extends ConsumerState<AddUserScreen> { | ||
// Form key for validation | ||
final _formKey = GlobalKey<FormState>(); | ||
// Controllers for form fields | ||
final _nameController = TextEditingController(); | ||
final _emailController = TextEditingController(); | ||
|
||
// Clean up controllers when the widget is disposed | ||
@override | ||
void dispose() { | ||
_nameController.dispose(); | ||
_emailController.dispose(); | ||
super.dispose(); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
// Function to pop the context to avoid using | ||
//context across async gaps | ||
void popContext() { | ||
Navigator.pop(context); | ||
} | ||
|
||
return Scaffold( | ||
appBar: AppBar(title: const Text('Add User')), | ||
body: Padding( | ||
padding: const EdgeInsets.all(16.0), | ||
child: Form( | ||
key: _formKey, | ||
child: Column( | ||
children: [ | ||
// Name input field with validation | ||
TextFormField( | ||
controller: _nameController, | ||
decoration: const InputDecoration(labelText: 'Name'), | ||
validator: (value) { | ||
if (value == null || value.isEmpty) { | ||
return 'Please enter a name'; | ||
} | ||
return null; | ||
}, | ||
), | ||
// Email input field with validation | ||
TextFormField( | ||
controller: _emailController, | ||
decoration: const InputDecoration(labelText: 'Email'), | ||
validator: (value) { | ||
if (value == null || value.isEmpty) { | ||
return 'Please enter an email'; | ||
} | ||
if (!value.contains('@')) { | ||
return 'Please enter a valid email'; | ||
} | ||
return null; | ||
}, | ||
), | ||
const SizedBox(height: 16), | ||
ElevatedButton( | ||
onPressed: () async { | ||
if (_formKey.currentState!.validate()) { | ||
final newUser = User( | ||
id: 0, // API will assign the real ID | ||
name: _nameController.text, | ||
email: _emailController.text, | ||
); | ||
// Add user and return to previous screen | ||
await ref.read(usersProvider.notifier).addUser(newUser); | ||
popContext(); | ||
} | ||
}, | ||
child: const Text('Add User'), | ||
), | ||
], | ||
), | ||
), | ||
), | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve form handling and user feedback.
The form could provide better feedback and validation.
Consider these improvements:
class AddUserScreenState extends ConsumerState<AddUserScreen> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
+ bool _isSubmitting = false;
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
void popContext() {
Navigator.pop(context);
}
+ String? validateEmail(String? value) {
+ if (value == null || value.isEmpty) {
+ return 'Please enter an email';
+ }
+ final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
+ if (!emailRegex.hasMatch(value)) {
+ return 'Please enter a valid email';
+ }
+ return null;
+ }
return Scaffold(
appBar: AppBar(title: const Text('Add User')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Name',
+ prefixIcon: Icon(Icons.person),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a name';
}
+ if (value.length < 2) {
+ return 'Name must be at least 2 characters';
+ }
return null;
},
+ textInputAction: TextInputAction.next,
),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email',
+ prefixIcon: Icon(Icons.email),
),
- validator: (value) {
- if (value == null || value.isEmpty) {
- return 'Please enter an email';
- }
- if (!value.contains('@')) {
- return 'Please enter a valid email';
- }
- return null;
- },
+ validator: validateEmail,
+ keyboardType: TextInputType.emailAddress,
+ textInputAction: TextInputAction.done,
),
const SizedBox(height: 16),
- ElevatedButton(
+ _isSubmitting
+ ? const CircularProgressIndicator()
+ : ElevatedButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
+ setState(() => _isSubmitting = true);
final newUser = User(
id: 0,
name: _nameController.text,
email: _emailController.text,
);
- await ref.read(usersProvider.notifier).addUser(newUser);
- popContext();
+ try {
+ await ref.read(usersProvider.notifier).addUser(newUser);
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('User added successfully'),
+ backgroundColor: Colors.green,
+ ),
+ );
+ popContext();
+ } catch (e) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Error: $e'),
+ backgroundColor: Colors.red,
+ ),
+ );
+ } finally {
+ setState(() => _isSubmitting = false);
+ }
}
},
child: const Text('Add User'),
),
],
),
),
),
);
}
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
class AddUserScreen extends ConsumerStatefulWidget { | |
const AddUserScreen({super.key}); | |
@override | |
AddUserScreenState createState() => AddUserScreenState(); | |
} | |
class AddUserScreenState extends ConsumerState<AddUserScreen> { | |
// Form key for validation | |
final _formKey = GlobalKey<FormState>(); | |
// Controllers for form fields | |
final _nameController = TextEditingController(); | |
final _emailController = TextEditingController(); | |
// Clean up controllers when the widget is disposed | |
@override | |
void dispose() { | |
_nameController.dispose(); | |
_emailController.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
// Function to pop the context to avoid using | |
//context across async gaps | |
void popContext() { | |
Navigator.pop(context); | |
} | |
return Scaffold( | |
appBar: AppBar(title: const Text('Add User')), | |
body: Padding( | |
padding: const EdgeInsets.all(16.0), | |
child: Form( | |
key: _formKey, | |
child: Column( | |
children: [ | |
// Name input field with validation | |
TextFormField( | |
controller: _nameController, | |
decoration: const InputDecoration(labelText: 'Name'), | |
validator: (value) { | |
if (value == null || value.isEmpty) { | |
return 'Please enter a name'; | |
} | |
return null; | |
}, | |
), | |
// Email input field with validation | |
TextFormField( | |
controller: _emailController, | |
decoration: const InputDecoration(labelText: 'Email'), | |
validator: (value) { | |
if (value == null || value.isEmpty) { | |
return 'Please enter an email'; | |
} | |
if (!value.contains('@')) { | |
return 'Please enter a valid email'; | |
} | |
return null; | |
}, | |
), | |
const SizedBox(height: 16), | |
ElevatedButton( | |
onPressed: () async { | |
if (_formKey.currentState!.validate()) { | |
final newUser = User( | |
id: 0, // API will assign the real ID | |
name: _nameController.text, | |
email: _emailController.text, | |
); | |
// Add user and return to previous screen | |
await ref.read(usersProvider.notifier).addUser(newUser); | |
popContext(); | |
} | |
}, | |
child: const Text('Add User'), | |
), | |
], | |
), | |
), | |
), | |
); | |
} | |
} | |
class AddUserScreen extends ConsumerStatefulWidget { | |
const AddUserScreen({super.key}); | |
@override | |
AddUserScreenState createState() => AddUserScreenState(); | |
} | |
class AddUserScreenState extends ConsumerState<AddUserScreen> { | |
final _formKey = GlobalKey<FormState>(); | |
final _nameController = TextEditingController(); | |
final _emailController = TextEditingController(); | |
bool _isSubmitting = false; | |
@override | |
void dispose() { | |
_nameController.dispose(); | |
_emailController.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
void popContext() { | |
Navigator.pop(context); | |
} | |
String? validateEmail(String? value) { | |
if (value == null || value.isEmpty) { | |
return 'Please enter an email'; | |
} | |
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); | |
if (!emailRegex.hasMatch(value)) { | |
return 'Please enter a valid email'; | |
} | |
return null; | |
} | |
return Scaffold( | |
appBar: AppBar(title: const Text('Add User')), | |
body: Padding( | |
padding: const EdgeInsets.all(16.0), | |
child: Form( | |
key: _formKey, | |
child: Column( | |
children: [ | |
TextFormField( | |
controller: _nameController, | |
decoration: const InputDecoration( | |
labelText: 'Name', | |
prefixIcon: Icon(Icons.person), | |
), | |
validator: (value) { | |
if (value == null || value.isEmpty) { | |
return 'Please enter a name'; | |
} | |
if (value.length < 2) { | |
return 'Name must be at least 2 characters'; | |
} | |
return null; | |
}, | |
textInputAction: TextInputAction.next, | |
), | |
TextFormField( | |
controller: _emailController, | |
decoration: const InputDecoration( | |
labelText: 'Email', | |
prefixIcon: Icon(Icons.email), | |
), | |
validator: validateEmail, | |
keyboardType: TextInputType.emailAddress, | |
textInputAction: TextInputAction.done, | |
), | |
const SizedBox(height: 16), | |
_isSubmitting | |
? const CircularProgressIndicator() | |
: ElevatedButton( | |
onPressed: () async { | |
if (_formKey.currentState!.validate()) { | |
setState(() => _isSubmitting = true); | |
final newUser = User( | |
id: 0, | |
name: _nameController.text, | |
email: _emailController.text, | |
); | |
try { | |
await ref.read(usersProvider.notifier).addUser(newUser); | |
ScaffoldMessenger.of(context).showSnackBar( | |
const SnackBar( | |
content: Text('User added successfully'), | |
backgroundColor: Colors.green, | |
), | |
); | |
popContext(); | |
} catch (e) { | |
ScaffoldMessenger.of(context).showSnackBar( | |
SnackBar( | |
content: Text('Error: $e'), | |
backgroundColor: Colors.red, | |
), | |
); | |
} finally { | |
setState(() => _isSubmitting = false); | |
} | |
} | |
}, | |
child: const Text('Add User'), | |
), | |
], | |
), | |
), | |
), | |
); | |
} | |
} |
The changes proposed by Also the failing code-generation check I don't fully understand yet. But I'll investigate it further asap :) |
this should fix the error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
examples/rest_api/pubspec.yaml (2)
1-4
: Remove trailing space after'none'
.The project metadata looks good, but there's a trailing space after
'none'
on line 3 that should be removed.-publish_to: 'none' +publish_to: 'none'🧰 Tools
🪛 YAMLlint (1.35.1)
[error] 3-3: trailing spaces
(trailing-spaces)
24-27
: Remove extra blank line in Flutter configuration.The Flutter configuration is appropriate, but there's an unnecessary blank line that could be removed for better readability.
flutter: - uses-material-design: true
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
examples/rest_api/pubspec.yaml
(1 hunks)examples/rest_api/test/user_test.mocks.dart
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- examples/rest_api/test/user_test.mocks.dart
🧰 Additional context used
🪛 YAMLlint (1.35.1)
examples/rest_api/pubspec.yaml
[error] 3-3: trailing spaces
(trailing-spaces)
🔇 Additional comments (3)
examples/rest_api/pubspec.yaml (3)
6-7
: LGTM!The SDK version constraint is well-defined and appropriate for modern Dart features.
16-22
: LGTM!The development dependencies are well-chosen for testing and development:
mockito
andbuild_runner
for generating mock classesflutter_lints
for static analysisflutter_test
for widget testing
11-14
: Verify dependency versions for security and compatibility.Let's check if we're using the latest stable versions of the dependencies.
✅ Verification successful
🌐 Web query:
What are the latest stable versions of flutter_riverpod, dio, and cupertino_icons packages on pub.dev?
💡 Result:
The latest stable versions of these Flutter packages on pub.dev are:
flutter_riverpod
- Version:
2.6.1
Released on October 22, 2024[5][9].
Includes updates likeAsyncNotifier.listenSelf
support and deprecation of olderRef
subclasses[5].dio
- Version:
5.8.0+1
Latest stable release as of February 2025[6].
A robust HTTP client with features like interceptors and request cancellation[2][6].cupertino_icons
- Version:
1.0.8
Last updated nine months before February 2025[4].
Provides Apple-style icons for Cupertino widgets with over 1,000 icons and backward compatibility for most legacy icons[3][4].These versions reflect the most recent stable releases available on pub.dev at the time of checking[4][5][6].
Citations:
- 1: https://pub.dev/packages/flutter_riverpod/versions/1.0.4/changelog
- 2: https://pub.dev/documentation/diox/latest/
- 3: https://docs.flutter.dev/release/breaking-changes/cupertino-icons-1.0.0
- 4: https://pub.dev/packages/cupertino_icons/versions
- 5: https://pub.dev/packages/flutter_riverpod/changelog
- 6: https://pub.dev/packages/dio/versions
- 7: https://pub.dev/packages/cupertino_icons_module/versions
- 8: https://github.com/polilluminato/awesome-pubdev
- 9: https://pub.dev/packages/flutter_riverpod/versions
- 10: https://pub.dev/documentation/dio_nexus/latest/
Dependency versions verified and up-to-date.
- flutter_riverpod: The declared version (^2.6.1) matches the latest stable release.
- cupertino_icons: The declared version (^1.0.8) is the latest stable release.
- dio: Although the latest stable version is 5.8.0+1, using ^5.8.0 is acceptable because build metadata is ignored by the version constraint, meaning it will allow the patch update automatically.
@rrousselGit it seems that the workflow failed due to a problem with the dart file. Can you rerun the jobs?
|
Related Issues
related to: #3932
Add example for handling
REST API
with riverpod.Please let me know when there is anything to improve :) I'll do it asap 💪 🚀
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]
).I have updated the
CHANGELOG.md
of the relevant packages.Changelog files must be edited under the form:
If this contains new features or behavior changes,
I have updated the documentation to match those changes.
Summary by CodeRabbit
New Features
Documentation
Testing