DEV Community

Cover image for Design Pattern in Flutter MVVM
Sukma Rizki
Sukma Rizki

Posted on

Design Pattern in Flutter MVVM

Purpose: The MVVM (Model-View-ViewModel) pattern is used to separate the UI (View) from the business logic and data (Model) in Flutter applications. This separation improves code maintainability, testability, and scalability.
Core Concepts:

Model: Represents the data and business logic.
View: Displays the data and listens to user interactions.
ViewModel: Acts as a mediator between the View and the Model. It handles the presentation logic and exposes data to the View.
Benefits:
Separation of Concerns: Keeps the UI code separate from the business logic.
Testability: Makes it easier to test the business logic and UI separately.
Maintainability: Simplifies the codebase and makes it easier to maintain.
Example Code:
Let’s create a simple counter app using the MVVM pattern.

Step 1: Create the Model


// models/counter_model.dart
class CounterModel {
  int counter;

  CounterModel({this.counter = 0});
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the ViewModel

// viewmodels/counter_view_model.dart
import 'package:flutter/material.dart';
import '../models/counter_model.dart';

class CounterViewModel extends ChangeNotifier {
  CounterModel _counterModel = CounterModel();

  int get counter => _counterModel.counter;

  void increment() {
    _counterModel.counter++;
    notifyListeners();
  }

  void decrement() {
    _counterModel.counter--;
    notifyListeners();
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Provide the ViewModel
Wrap your main app widget with a ChangeNotifierProvider:

// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'viewmodels/counter_view_model.dart';
import 'views/counter_page.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CounterViewModel(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterPage(),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create the View
Use the Consumer widget to listen to changes and rebuild the UI:

// views/counter_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../viewmodels/counter_view_model.dart';

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('MVVM Counter'),
      ),
      body: Center(
        child: Consumer<CounterViewModel>(
          builder: (context, counterViewModel, child) {
            return Text(
              'Counter: ${counterViewModel.counter}',
              style: TextStyle(fontSize: 24),
            );
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => context.read<CounterViewModel>().increment(),
            child: Icon(Icons.add),
          ),
          SizedBox(height: 8),
          FloatingActionButton(
            onPressed: () => context.read<CounterViewModel>().decrement(),
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the CounterModel class holds the data. The CounterViewModel class acts as a mediator between the model and the UI, handling the presentation logic. The CounterPage widget is the View, which uses the CounterViewModel to display and interact with the counter data.

By using the MVVM pattern, we achieve a clean separation of concerns, making our code more maintainable and testable.

Top comments (0)