Top 5 Popular Architectures for Building Large-Scale Mobile Apps in Flutter

Mobile Apps in Flutter

Here are five popular architectures for building large-scale mobile apps in Flutter, along with a brief explanation and an example:

1. BLoC (Business Logic Component) Architecture:

  • Explanation: BLoC separates the business logic from the UI, using streams to handle data flow. It consists of three main components: StreamController (to manage data streams), Sink (for input), and Stream (for output).
  • Example: Using the flutter_bloc package, managing state with BlocProvider and BlocBuilder to handle different states and UI updates based on data changes.

Certainly! I’ll provide you with a basic Flutter project that demonstrates the BLoC (Business Logic Component) architecture using the flutter_bloc package for managing state.

To create a simple counter app using BLoC architecture:

Set Up the Project:

Create a new Flutter project or use an existing one. Make sure to add the required dependencies in your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.0 # Check for the latest version

Implement BLoC:

Create a file named counter_bloc.dart to define your BLoC logic:

import 'package:flutter_bloc/flutter_bloc.dart';

// Define events
enum CounterEvent { increment, decrement }

// Define BLoC
class CounterBloc extends Bloc<CounterEvent, int> {
  CounterBloc() : super(0);

  @override
  Stream<int> mapEventToState(CounterEvent event) async* {
    switch (event) {
      case CounterEvent.increment:
        yield state + 1;
        break;
      case CounterEvent.decrement:
        yield state - 1;
        break;
    }
  }
}

Create UI Components:

Build the UI components in main.dart to interact with the BLoC:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_bloc.dart'; // Import your BLoC

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'BLoC Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: BlocProvider(
        create: (_) => CounterBloc(), // Provide the CounterBloc
        child: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('BLoC Demo'),
      ),
      body: Center(
        child: BlocBuilder<CounterBloc, int>(
          builder: (context, count) {
            return Text('Count: $count', style: TextStyle(fontSize: 24));
          },
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            onPressed: () => counterBloc.add(CounterEvent.increment),
            child: Icon(Icons.add),
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () => counterBloc.add(CounterEvent.decrement),
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

Run the App:

Run the app using flutter run in your terminal or your IDE’s run command.

This example demonstrates a simple counter app where tapping the floating action buttons increments or decrements the count, managed by the CounterBloc. The UI is updated using BlocBuilder whenever the state changes in the CounterBloc.

Feel free to expand this basic example by adding more complex logic, multiple BLoCs, or integrating API calls to understand how the BLoC architecture helps in separating business logic from the UI.

2. Provider Architecture:

  • Explanation: Provider is a simple yet powerful architecture focusing on state management. It allows the app to listen to changes in a specific part of the widget tree and update accordingly.
  • Example: Utilizing the provider package to create providers for different data models, then using Consumer widgets to listen and update UI when changes occur.

Certainly! Here’s a simple Flutter project that demonstrates the Provider architecture for state management:

Add Required Dependency:

Add the provider package to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  provider: ^5.0.0

 

Replace the Contents of lib/main.dart:

Replace the default content in lib/main.dart with the following code:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }

  void decrement() {
    _count--;
    notifyListeners();
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MaterialApp(
        title: 'Provider Architecture Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Provider Architecture Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Count:',
              style: TextStyle(fontSize: 20),
            ),
            Text(
              '${counter.count}',
              style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            onPressed: () => counter.increment(),
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () => counter.decrement(),
            tooltip: 'Decrement',
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}

Understand the Demo Project:

  • This demo uses the provider package to manage state with the Counter class.
  • The Counter class extends ChangeNotifier, which notifies listeners (the UI) whenever the count changes.
  • ChangeNotifierProvider is used to provide the Counter instance to the widget tree.
  • The MyHomePage widget uses Provider.of<Counter>(context) to access the Counter instance and display the count.
  • Tapping the floating action buttons increments or decrements the count, updating the UI through Provider’s state management.

Run the App:

Run the app using flutter run in your terminal or IDE to see the demonstration of state management using Provider architecture in Flutter.

This example illustrates a simple counter app using Provider architecture. Feel free to expand and modify this code to understand Provider’s capabilities in managing state for more complex applications.

3. MVVM (Model-View-ViewModel):

  • Explanation: MVVM separates the UI (View) from the business logic (ViewModel) with the help of data-binding. The View observes changes in the ViewModel and updates accordingly.
  • Example: Using Provider for state management and separating the UI logic from business logic by creating separate ViewModel classes.

Certainly! Here’s a basic Flutter project that demonstrates the MVVM (Model-View-ViewModel) architecture:

Add Required Dependency:

Add the provider package to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  provider: ^5.0.0

Replace the Contents of lib/main.dart:

Replace the default content in lib/main.dart with the following code:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

// Model Class
class Counter {
  int count;

  Counter(this.count);
}

// ViewModel Class
class CounterViewModel {
  final Counter _counter = Counter(0);

  int get count => _counter.count;

  void increment() {
    _counter.count++;
  }

  void decrement() {
    _counter.count--;
  }
}

// View
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => CounterViewModel(),
      child: MaterialApp(
        title: 'MVVM Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counterViewModel = Provider.of<CounterViewModel>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('MVVM Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Count:',
              style: TextStyle(fontSize: 20),
            ),
            Text(
              '${counterViewModel.count}',
              style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            onPressed: () => counterViewModel.increment(),
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () => counterViewModel.decrement(),
            tooltip: 'Decrement',
            child: Icon(Icons.remove),
          ),
        ],
      ),
    );
  }
}


Understand the Demo Project:

  • This demo implements MVVM architecture using the provider package.
  • The Counter class represents the model.
  • CounterViewModel acts as the ViewModel, managing the state of the counter.
  • ChangeNotifierProvider provides the CounterViewModel instance to the widget tree.
  • The MyHomePage widget uses Provider.of<CounterViewModel>(context) to access the CounterViewModel instance and display the count.
  • Tapping the floating action buttons increments or decrements the count through the ViewModel.

Run the App:

Run the app using flutter run in your terminal or IDE to see the demonstration of MVVM architecture in a simple Flutter application.

This example demonstrates a basic counter app using the MVVM architecture. Modify and expand upon this code to understand how MVVM separates concerns and improves code maintainability in larger-scale Flutter applications.

4. Redux Architecture:

  • Explanation: Redux is a predictable state container that employs a single immutable store for the whole application. It helps manage complex states by dispatching actions to update the store.
  • Example: Implementing Redux using packages like redux and flutter_redux, creating actions, reducers, and a store to manage the app’s state.

Certainly! Implementing Redux architecture in Flutter involves using packages like redux and flutter_redux. Below is a basic demonstration of a counter app using Redux architecture in Flutter:

Add Required Dependencies:

Add the redux and flutter_redux packages to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  redux: ^5.0.0
  flutter_redux: ^0.8.2

Replace the Contents of lib/main.dart:

Replace the default content in lib/main.dart with the following code:

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';

void main() {
  runApp(MyApp());
}

// Actions
enum ActionType { increment, decrement }

// Reducer
int counterReducer(int state, dynamic action) {
  if (action == ActionType.increment) {
    return state + 1;
  } else if (action == ActionType.decrement) {
    return state - 1;
  }
  return state;
}

// Redux Store
final store = Store<int>(
  counterReducer,
  initialState: 0,
);

// View
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StoreProvider<int>(
      store: store,
      child: MaterialApp(
        title: 'Redux Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Redux Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            StoreConnector<int, String>(
              converter: (store) => store.state.toString(),
              builder: (context, count) {
                return Text(
                  'Count: $count',
                  style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: <Widget>[
          StoreConnector<int, VoidCallback>(
            converter: (store) {
              return () => store.dispatch(ActionType.increment);
            },
            builder: (context, callback) {
              return FloatingActionButton(
                onPressed: callback,
                tooltip: 'Increment',
                child: Icon(Icons.add),
              );
            },
          ),
          SizedBox(height: 10),
          StoreConnector<int, VoidCallback>(
            converter: (store) {
              return () => store.dispatch(ActionType.decrement);
            },
            builder: (context, callback) {
              return FloatingActionButton(
                onPressed: callback,
                tooltip: 'Decrement',
                child: Icon(Icons.remove),
              );
            },
          ),
        ],
      ),
    );
  }
}

 

Understand the Demo Project:

  • This demo implements the Redux architecture using redux and flutter_redux packages.
  • ActionType defines the available actions (increment and decrement).
  • counterReducer is the reducer function that updates the state based on actions.
  • store is the Redux store that holds the application state.
  • MyApp widget wraps the entire app with StoreProvider to provide the Redux store to the widget tree.
  • MyHomePage widget uses StoreConnector to connect the UI to the Redux store. It displays the count and dispatches actions when the buttons are pressed.

Run the App:

Run the app using flutter run in your terminal or IDE to see the Redux architecture in action for a simple counter app.

This example demonstrates a basic counter app using Redux architecture. Modify and expand upon this code to understand how Redux manages application state in Flutter and apply it to more complex app scenarios.

5. Clean Architecture:

  • Explanation: Clean Architecture emphasizes separation of concerns by dividing the app into layers (Presentation, Domain, Data). It aims for independence between layers, making the codebase more maintainable and testable.
  • Example: Creating different layers for UI components, use cases, and data sources, ensuring the UI doesn’t directly interact with data sources.

Implementing Clean Architecture in a Flutter project involves structuring the codebase into different layers, such as Presentation, Domain, and Data layers, to achieve separation of concerns and improve maintainability. Here’s a basic demonstration of a Flutter project organized using Clean Architecture principles:

Organize the Project Structure:

Organize the project structure into different folders representing layers:

- lib/
  - data/
    - repositories/
    - models/
    - datasources/
  - domain/
    - entities/
    - repositories/
    - usecases/
  - presentation/
    - screens/
    - widgets/

Add Required Packages:

Although Clean Architecture is more about project structure and principles, you might use additional packages based on your project needs.

Example Implementation:

Here’s an abstract example demonstrating how code might be organized within each layer:

  • Data Layer:
  • datasources: Implement data sources (local, remote) that provide data to the app.
  • models: Define data models used by the app.
  • repositories: Implement repository interfaces that define methods for data operations.
  • Domain Layer:
  • entities: Define business entities representing core domain models.
  • repositories: Define repository interfaces to abstract data access.
  • usecases: Implement use cases representing app functionalities.
  • Presentation Layer:
  • screens: Build UI screens using Flutter widgets.
    widgets: Create reusable UI components.

Here’s an abstract code structure example (not specific implementation):

// Example structure - update according to your project needs
lib/
  data/
    datasources/
      remote_datasource.dart
      local_datasource.dart
    models/
      user.dart
    repositories/
      user_repository.dart
  domain/
    entities/
      user_entity.dart
    repositories/
      user_repository_interface.dart
    usecases/
      get_user_usecase.dart
  presentation/
    screens/
      user_screen.dart
    widgets/
      user_list_widget.dart

Implement Layers:

Implement the layers according to Clean Architecture principles:

  • Data layer interacts with external data sources.
  • Domain layer contains business logic and interacts with data layer.
  • Presentation layer builds UI and communicates with domain layer.

Run the App:

Run the app using flutter run to see the basic structure. This abstract example demonstrates the organization of code into different layers following Clean Architecture principles. You’ll need to implement specific functionalities and logic according to your project’s requirements.

Remember, Clean Architecture is about maintaining separation of concerns and making the codebase more maintainable and testable. The actual implementation may vary based on project complexity and requirements.

Author

  • Harish Sharma

    Entrepreneur and Technology Enthusiast | Started Varshyl Technologies, a web and mobile application development company, helping companies build and promote their digital presence. Co-founded Snapworks - a mobile first communication platform for schools. Outside VT, enjoys his morning workouts, reading biographies and golf.