Mobile Application Development - Basic State Management & Architecture

Agile Software Development

  • Agile is a software development methodology.
  • Focuses on flexibility, collaboration, and efficiency.
  • Allows teams to deliver quality products.

Layered Architecture

  • Introduces a clear separation of concerns between different parts of the system.
  • Makes code easier to read, maintain, and test.
  • Layers include:
    • Presentation layer
    • Application layer
    • Domain layer
    • Data layer

Model-View Separation

  • Models: Classes that deal with the data for an app.
  • Views: Classes that present data on screen (i.e., Widgets).
  • Refer to Handout 7: Basic State Management (uploaded in LMS) for detailed examples.

Domain Model

  • The domain model is a conceptual model of the domain that incorporates both behavior and data.

Model

  • In Flutter, the Model can be any Dart class that contains the data and the logic needed to manipulate the data.
  • Example: An application that displays a list of users. The Model can be a class that contains an array of User objects and methods to add, delete, and update the users.
  • The Model folder should not contain any UI code.
  • Purpose: To store the data and logic of the application, keeping it separate from the presentation.

Model - Example

  • User
    • uid
    • email
  • Cart
    • items
    • total
  • Item
    • productId
    • quantity
  • Order
    • id
    • userId
    • items
    • status
    • date
    • total
  • Product
    • id
    • imageUrl
    • title
    • price
    • availableQuantity

Code Example:

// model/user.dart
class User {
  final String name;
  final String email;
  User({this.name, this.email});
}

// model/user_repository.dart
import 'package: flutter/widgets.dart';
import 'package:your_project/model/user.dart';

class UserRepository {
  Future<User> getUser() async {
    // Fetch user data from a remote API or a database
    return User (name: "John Doe", email: "johndoe@example.com");
  }
}

View

  • In Flutter, the View is created using the widgets provided by the Flutter framework.
  • Widgets are used to build the UI and display the data from the Model.
  • Example: Use a ListView widget to display the list of users from the Model.
  • The main goal of the View folder is to present the data from the Model layer in a way that is easy to understand and interact with.
  • Create different folders for different functionalities to keep code organized and maintainable.
  • By keeping the View folder clean and simple, it makes it easier to manage and update the visual appearance of the application without affecting the underlying data and business logic.

View - Example

// view/widgets/user_card.dart
import 'package: flutter/material.dart';
import 'package:your_project/model/user.dart';

class UserCard extends StatelessWidget {
  final User user;
  UserCard({this.user});

  @override
  Widget build (BuildContext context) {
    return Card(
      child: Padding (
        padding: EdgeInsets.all(8.0),
        child: Column (
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(
              user.name,
              style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold),
            ),
            SizedBox (height: 8.0),
            Text(
              user.email,
              style: TextStyle(fontSize: 14.0),
            ),
          ],
        ),
      )
    );
  }
}

//view/pages/home_page.dart
import 'package: flutter/material.dart';
import 'package:your_project/model/user_repository.dart';
import 'package:your_project/view/widgets/user_card.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  UserRepository _userRepository = UserRepository();
  User _user;

  @override
  void initState() {
    super.initState();
    _fetchUser();
  }

  void _fetchUser() async {
    User user = await _userRepository.getUser();
    setState(() {
      _user = user;
    });
  }

  @override
  Widget build (BuildContext context) {
    return Scaffold (
      appBar AppBar (
        title: Text("Home"),
      ),
      body: Center (
        child: _user == null
            ? CircularProgress Indicator()
            : UserCard (user: _user),
      ),
    );
  }
}

Passing Data from Model to View

  • Data Classes should be in the widget tree to take advantage of Hot Reload.
  • Models are not widgets, so there is nothing to build onto the screen.
  • Solution: InheritedWidgets.
  • Its job is to pass data down to its children, but from a user's perspective, it's invisible.

InheritedWidgets

  • InheritedWidget provides an easy way to grab data from a shared ancestor by creating a state widget that wraps a common ancestor in the widget tree.
  • Example:
final studentState = StudentState.of(context);

.of(context)

  • The of(context) call takes the build context (a handle to the current widget location) and returns the nearest ancestor in the tree that matches the StudentState type.
  • InheritedWidgets also offer an updateShouldNotify() method, which Flutter calls to determine whether a state change should trigger a rebuild of child widgets that use it.

Layered Architecture - Additional Components

  • Controllers
  • Repositories
  • Services

MV* Patterns

  • MVC: Model-View-Controller
  • MVP: Model-View-Presenter
  • MVVM: Model-View-ViewModel

MV* Patterns - Diagram

  • MVC
    • Input -> View -> Controller -> Model
  • MVP
    • Input -> View -> Presenter -> Model
  • MVVM
    • Input -> View -> ViewModel -> Model

Controllers

  • In Flutter, the Controller can be a Dart class that connects the Model and View and handles user input.
  • The Controller can listen to events from the View and update the Model accordingly.
  • Example: If the user clicks on a button in the View, the Controller can respond to the event and perform some action, such as adding a new user to the list.
  • The Controller folder is responsible for managing communication between the Model and View layers.
  • Responsibilities:
    1. Handles user input
    2. Update Model
    3. Notify the View
    4. Validate data
    5. Manage navigation

Controllers - Example: LoginController

  • This class handles the logic for handling the login button press.
  • It uses the Firebase Auth API to perform authentication.
  • If successful, navigates to the home screen.
  • If the authentication fails, it sets the error message to display to the user.

Utils

  • Used to store utility functions that are used across the application.
  • These functions are often used to perform tasks that are repetitive, such as formatting dates, validating input, or making API calls.

Constants

  • Used to store constant values that are used across the application.
  • Examples: API endpoint URLs, color codes, and font sizes.
  • It's also a good practice to store padding and margin values for the overall application in this folder.

Repositories

  • Repositories are found in the data layer.
  • Job: Isolate domain models (or entities) from the implementation details of the data sources in the data layer.
  • Convert data transfer objects to validated entities that are understood by the domain layer.
  • (Optionally) perform operations such as data caching.
  • The repository pattern is very handy if your app has a complex data layer with many different endpoints that return unstructured data (such as JSON) that you want to isolate from the rest of the app.
  • Use cases:
    • Talking to REST APIs
    • Talking to local or remote databases (e.g., Sembast, Hive, Firestore, etc.)
    • Talking to device-specific APIs (e.g., permissions, camera, location, etc.)

Services

  • Services are classes that offer a specific functionality.
  • A Service is a class that uses methods that enable it to provide a specialized feature, emphasizing specialized.
  • A Service class offers just one distinct input to the app.
  • Examples:
    • ApiService
    • LocalStorageService
    • ConnectivityService
    • ThemeService
    • MediaService