o use the provider package with ChangeNotifier for state management, Firestore as the database, and follow the MVVM (Model-View-ViewModel) architecture, you can organize your project as follows:
my_flutter_app/
├── lib/
│ ├── main.dart
│ ├── models/
│ │ ├── user_model.dart
│ ├── viewmodels/
│ │ ├── user_viewmodel.dart
│ ├── providers/
│ │ ├── user_provider.dart
│ ├── services/
│ │ ├── user_service.dart
│ └── screens/
│ ├── user_list_screen.dart
│ ├── user_modify_screen.dart
│ ├── user_detail_screen.dart
└── pubspec.yaml
Here's an explanation of the components in this structure:
-
models directory: Contains the data models like UserModel. These models represent the structure of your data but do not include any logic or behavior.
-
viewmodels directory: Contains the view models, such as UserViewModel, which are responsible for preparing and managing the data to be displayed in the UI. They act as intermediaries between the UI (Views) and the data.
-
providers directory: Contains classes like UserProvider, which use the ChangeNotifier package to manage state and notify listeners when data changes. These providers expose data to the UI.
-
services directory: Contains classes like UserService, responsible for interacting with Firestore to fetch, update, and delete user data. These services handle the data layer and communicate with external sources.
-
screens directory: Contains the UI components, including screens like UserListScreen, UserModifyScreen, and UserDetailScreen. These screens use the view models to display data and interact with users.
Using this architecture, the UserViewModel can listen to changes from UserProvider, which is backed by UserService for Firestore interactions. This architecture aligns with the MVVM pattern, separating the concerns of data modeling, view models, and data services, making your codebase organized and maintainable.
class UserModel {
final String id;
String name;
int age;
UserModel({
required this.id,
required this.name,
required this.age,
});
}
import 'package:flutter/foundation.dart';
import 'package:your_app_name/models/user_model.dart';
import 'package:your_app_name/providers/user_provider.dart';
class UserViewModel extends ChangeNotifier {
final UserProvider userProvider;
UserViewModel(this.userProvider);
List<UserModel> get users => userProvider.users;
// Add any view model logic here
// Example: Create a new user
void createUser(String name, int age) {
final newUser = UserModel(
id: UniqueKey().toString(),
name: name,
age: age,
);
userProvider.createUser(newUser);
}
// Example: Update user information
void updateUser(String userId, String newName, int newAge) {
final updatedUser = UserModel(
id: userId,
name: newName,
age: newAge,
);
userProvider.updateUser(updatedUser);
}
// Example: Delete a user
void deleteUser(String userId) {
userProvider.deleteUser(userId);
}
}
import 'package:flutter/foundation.dart';
import 'package:your_app_name/models/user_model.dart';
class UserProvider extends ChangeNotifier {
final List<UserModel> _users = [];
List<UserModel> get users => _users;
// Create a new user
void createUser(UserModel user) {
_users.add(user);
notifyListeners();
}
// Read (Retrieve) a user by their ID
UserModel? readUserById(String id) {
return _users.firstWhere((user) => user.id == id, orElse: () => null);
}
// Update a user's information
void updateUser(UserModel updatedUser) {
final index = _users.indexWhere((user) => user.id == updatedUser.id);
if (index != -1) {
_users[index] = updatedUser;
notifyListeners();
}
}
// Delete a user
void deleteUser(String id) {
_users.removeWhere((user) => user.id == id);
notifyListeners();
}
}
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:your_app_name/models/user_model.dart';
class UserService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final CollectionReference _usersCollection = FirebaseFirestore.instance.collection('users');
// Fetch user data from Firestore
Future<List<UserModel>> fetchUsers() async {
final usersSnapshot = await _usersCollection.get();
return usersSnapshot.docs.map((doc) {
final data = doc.data() as Map<String, dynamic>;
return UserModel(
id: doc.id,
name: data['name'] as String,
age: data['age'] as int,
);
}).toList();
}
// Create a new user in Firestore
Future<void> createUser(UserModel user) async {
await _usersCollection.doc(user.id).set({
'name': user.name,
'age': user.age,
});
}
// Update a user's information in Firestore
Future<void> updateUser(UserModel updatedUser) async {
await _usersCollection.doc(updatedUser.id).update({
'name': updatedUser.name,
'age': updatedUser.age,
});
}
// Delete a user from Firestore
Future<void> deleteUser(String id) async {
await _usersCollection.doc(id).delete();
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/models/user_model.dart';
import 'package:your_app_name/viewmodels/user_viewmodel.dart';
class UserListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final userViewModel = Provider.of<UserViewModel>(context);
return Scaffold(
appBar: AppBar(
title: Text('User List'),
),
body: ListView.builder(
itemCount: userViewModel.users.length,
itemBuilder: (context, index) {
final user = userViewModel.users[index];
return ListTile(
title: Text(user.name),
subtitle: Text('Age: ${user.age.toString()}'),
onTap: () {
// Navigate to the user detail screen when a user is tapped
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => UserDetailScreen(userId: user.id),
));
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Navigate to the user creation screen when the FAB is tapped
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => UserModifyScreen(isCreating: true),
));
},
child: Icon(Icons.add),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/viewmodels/user_viewmodel.dart';
class UserModifyScreen extends StatefulWidget {
final bool isCreating;
final String userId;
UserModifyScreen({required this.isCreating, this.userId = ''});
@override
_UserModifyScreenState createState() => _UserModifyScreenState();
}
class _UserModifyScreenState extends State<UserModifyScreen> {
final TextEditingController nameController = TextEditingController();
final TextEditingController ageController = TextEditingController();
@override
void initState() {
super.initState();
if (!widget.isCreating) {
// Fetch user data if not in create mode
final userViewModel = Provider.of<UserViewModel>(context, listen: false);
final user = userViewModel.getUserById(widget.userId);
if (user != null) {
nameController.text = user.name;
ageController.text = user.age.toString();
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.isCreating ? 'Create User' : 'Edit User'),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: nameController,
decoration: InputDecoration(labelText: 'Name'),
),
TextField(
controller: ageController,
decoration: InputDecoration(labelText: 'Age'),
keyboardType: TextInputType.number,
),
ElevatedButton(
onPressed: () {
// Save or update user when the button is tapped
final userViewModel = Provider.of<UserViewModel>(context, listen: false);
if (widget.isCreating) {
userViewModel.createUser(nameController.text, int.tryParse(ageController.text) ?? 0);
} else {
userViewModel.updateUser(widget.userId, nameController.text, int.tryParse(ageController.text) ?? 0);
}
Navigator.of(context).pop();
},
child: Text(widget.isCreating ? 'Create' : 'Save'),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/models/user_model.dart';
import 'package:your_app_name/viewmodels/user_viewmodel.dart';
class UserDetailScreen extends StatelessWidget {
final String userId;
UserDetailScreen({required this.userId});
@override
Widget build(BuildContext context) {
final userViewModel = Provider.of<UserViewModel>(context);
final UserModel? user = userViewModel.readUserById(userId);
return Scaffold(
appBar: AppBar(
title: Text('User Detail'),
),
body: user != null
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
title: Text('Name'),
subtitle: Text(user.name),
),
ListTile(
title: Text('Age'),
subtitle: Text(user.age.toString()),
),
],
)
: Center(
child: Text('User not found'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Navigate to the user modification screen when the FAB is tapped
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => UserModifyScreen(isCreating: false, userId: userId),
));
},
child: Icon(Icons.edit),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:your_app_name/providers/user_provider.dart';
import 'package:your_app_name/viewmodels/user_viewmodel.dart';
import 'package:your_app_name/screens/user_list_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'User Management App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ChangeNotifierProvider(
create: (context) => UserProvider(),
child: UserListScreen(),
),
);
}
}
name: your_app_name
description: A new Flutter application
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0 #0+1
environment:
sdk: '>=2.19.2 <3.0.0'
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
# Replace with your specific version
provider: ^6.0.5
cloud_firestore: ^4.11.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
assets:
# Add any assets you want to include (e.g., images, fonts)
- assets/
# Add other configurations as needed