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
- Cart
- Item
- 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.
- 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:
- Handles user input
- Update Model
- Notify the View
- Validate data
- 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