MVVM in Flutter with bloc
MVVM stands for Model-View-ViewModel, which is a software architectural pattern used to separate the concerns of the user interface (View) from the business logic and data (Model) in an application. MVVM aims to provide a clean separation of concerns and improve maintainability and testability of the code.
Here’s a brief explanation of each component in MVVM:
- Model: The Model represents the data and business logic of the application. It encapsulates the data structures, operations, and rules that govern the application’s behavior. It can include data retrieval from databases, APIs, or any other data sources, as well as data manipulation and validation.
- View: The View represents the user interface of the application. It defines how the data is presented to the user and how user interactions are captured. The View can be anything from a simple UI component like a button or a complex screen with multiple widgets. In MVVM, the View is kept as lightweight as possible and does not contain any business logic.
- ViewModel: The ViewModel acts as an intermediary between the View and the Model. It exposes the necessary data and operations from the Model to the View and provides the logic to handle user interactions. The ViewModel transforms the data from the Model into a form that can be easily consumed by the View. It also captures user input from the View and updates the Model accordingly.
6.The key idea behind MVVM is the use of data binding. The View binds to properties and commands exposed by the ViewModel, allowing automatic synchronization between the UI and the underlying data. When the data in the Model changes, it triggers notifications that update the View through data binding. Similarly, when the user interacts with the View, the ViewModel handles the events and updates the Model as needed.
MVVM promotes separation of concerns, as the View is responsible for UI rendering only, the Model focuses on data and business logic, and the ViewModel handles the communication and transformation between the View and the Model. This separation enables better code organization, maintainability, and testability, as each component can be developed and tested independently.
MVVM is commonly used in frameworks like WPF (Windows Presentation Foundation) for desktop applications and frameworks like Flutter and Xamarin for cross-platform mobile app development.
Let’s work on a simple scenario of a note-taking application. This application will have basic functionality where users can view their notes, add new notes, and edit existing notes.
First, create the project file and add the necessary packages. In this example, we will use the flutter_bloc package, so add the following dependency to the pubspec.yaml file:
dependencies:
flutter_bloc: ^7.0.0
Next, create three folders named “models”, “views”, and “viewmodels” under the lib directory. These folders will contain the data models, user interface, and ViewModels, respectively.
Step 1: Creating the Model Under the models folder, create a class named Note. This class will represent the structure of the notes. For example:
class Note {
String title;
String content;
Note({
required this.title,
required this.content,
});
}
Step 2: Creating the ViewModel Under the viewmodels folder, create a class named NoteViewModel. This class will be responsible for handling data processing and communication with the View. For example:
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:your_app/models/note.dart';
class NoteViewModel extends Cubit<List<Note>> {
NoteViewModel() : super([]);
void addNote(Note note) {
state.add(note);
emit(List.from(state));
}
void updateNote(int index, Note note) {
state[index] = note;
emit(List.from(state));
}
void deleteNote(int index) {
state.removeAt(index);
emit(List.from(state));
}
}
This ViewModel is derived from the Cubit class and maintains a state list inside it to hold the notes. The addNote, updateNote, and deleteNote functions update the state to add, update, or delete notes and notify the changes using the emit function.
Step 3: Creating the View Under the views folder, create a widget named NoteListView. This widget will display the list of notes and allow users to add new notes or edit existing notes. For example:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:your_app/models/note.dart';
import 'package:your_app/viewmodels/note_viewmodel.dart';
class NoteListView extends StatelessWidget {
final NoteViewModel viewModel = NoteViewModel();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Note App'),
),
body: BlocBuilder<NoteViewModel, List<Note>>(
cubit: viewModel,
builder: (context, notes) {
return ListView.builder(
itemCount: notes.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(notes[index].title),
subtitle: Text(notes[index].content),
onTap: () {
_editNote(context, notes[index], index);
},
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_addNote(context);
},
child: Icon(Icons.add),
),
);
}
void _addNote(BuildContext context) {
showDialog(
context: context,
builder: (context) {
String title = '';
String content = '';
return AlertDialog(
title: Text('Add Note'),
content: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: 'Title',
),
onChanged: (value) {
title = value;
},
),
TextField(
decoration: InputDecoration(
labelText: 'Content',
),
onChanged: (value) {
content = value;
},
),
],
),
actions: [
FlatButton(
onPressed: () {
Note note = Note(
title: title,
content: content,
);
viewModel.addNote(note);
Navigator.of(context).pop();
},
child: Text('Add'),
),
],
);
},
);
}
void _editNote(BuildContext context, Note note, int index) {
showDialog(
context: context,
builder: (context) {
String title = note.title;
String content = note.content;
return AlertDialog(
title: Text('Edit Note'),
content: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: 'Title',
),
onChanged: (value) {
title = value;
},
controller: TextEditingController(text: title),
),
TextField(
decoration: InputDecoration(
labelText: 'Content',
),
onChanged: (value) {
content = value;
},
controller: TextEditingController(text: content),
),
],
),
actions: [
FlatButton(
onPressed: () {
Note updatedNote = Note(
title: title,
content: content,
);
viewModel.updateNote(index, updatedNote);
Navigator.of(context).pop();
},
child: Text('Update'),
),
FlatButton(
onPressed: () {
viewModel.deleteNote(index);
Navigator.of(context).pop();
},
child: Text('Delete'),
),
],
);
},
);
}
}
This NoteListView widget uses Scaffold to create a basic user interface. BlocBuilder listens to the NoteViewModel and updates the ListView based on the list of notes in the ViewModel. It creates a ListTile for each note, which is made clickable. The _addNote and _editNote functions display dialogs for adding or editing notes and update the ViewModel using the respective functions.
Finally, in the main.dart file, use NoteListView as the main widget:
import 'package:flutter/material.dart';
import 'package:your_app/views/note_list_view.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Note App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: NoteListView(),
);
}
}
That’s it! You have now created a note-taking application with a simple MVVM structure. The ViewModel handles user interactions, updates the data model, and uses Cubit to update the user interface without a direct dependency between the data and the user interface.