Skip to content

Commit c7a1bb3

Browse files
authored
Merge pull request #7 from flutter-news-app-full-source-code/enhance_mobile_client_docs
Enhance mobile client docs
2 parents 652414e + 9d1dad6 commit c7a1bb3

File tree

11 files changed

+485
-192
lines changed

11 files changed

+485
-192
lines changed

astro.config.mjs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export default defineConfig({
110110
collapsed: true,
111111
items: [
112112
{ label: 'Theming & Customization', link: '/web-dashboard/guides/theming-and-customization' },
113-
{ label: 'Adding a New Language', link: '/web-dashboard/guides/adding-a-new-language' },
113+
{ label: 'Localization', link: '/web-dashboard/guides/localization' },
114114
],
115115
},
116116
{
@@ -134,10 +134,21 @@ export default defineConfig({
134134
label: 'Architecture',
135135
collapsed: true,
136136
items: [
137-
{ label: 'Routing', link: '/mobile-client/architecture/routing' },
137+
{ label: 'Overview', link: '/mobile-client/architecture/' },
138+
{ label: 'State Management (BLoC)', link: '/mobile-client/architecture/state-management' },
139+
{ label: 'Routing & Navigation', link: '/mobile-client/architecture/routing' },
140+
{ label: 'Error Handling', link: '/mobile-client/architecture/error-handling' },
138141
{ label: 'Shared Components', link: '/mobile-client/architecture/shared-components' },
139142
],
140143
},
144+
{
145+
label: 'Guides',
146+
collapsed: true,
147+
items: [
148+
{ label: 'Styling & Theming', link: '/mobile-client/guides/styling-and-theming' },
149+
{ label: 'Localization', link: '/mobile-client/guides/localization' },
150+
],
151+
},
141152
{
142153
label: 'Features',
143154
collapsed: true,
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
title: Error Handling
3+
description: Learn how errors and exceptions are managed across the application layers.
4+
---
5+
import { Aside } from '@astrojs/starlight/components';
6+
7+
The mobile client employs a standardized and robust error handling strategy to ensure that issues are managed gracefully and that users are presented with clear, helpful feedback. The strategy is built upon a set of custom exception classes defined in the shared `core` package.
8+
9+
### The Standardized Exceptions
10+
11+
All exceptions that represent business logic or data fetching errors are subtypes of `HttpException`. This provides a consistent set of errors that can be handled across the entire application. Examples include:
12+
13+
- `NotFoundException`: Thrown when a requested resource does not exist.
14+
- `NetworkException`: Thrown for connectivity issues like timeouts or DNS failures.
15+
- `UnauthorizedException`: Thrown for authentication or authorization failures.
16+
- `OperationFailedException`: A general-purpose exception for other server-side or unexpected failures.
17+
18+
<Aside>
19+
You can find the full set of standardized exceptions in the `packages/core/lib/src/exceptions/` directory.
20+
</Aside>
21+
22+
### The Flow of an Exception
23+
24+
The error handling strategy follows the application's layered architecture, ensuring that exceptions are caught and handled at the appropriate level.
25+
26+
1. **Data Layer**: The `DataApi` client is responsible for making HTTP requests. It wraps all `try/catch` blocks around these requests. If a `DioException` (from the `http_client` package) occurs, an `ErrorInterceptor` maps it to one of the standardized `HttpException` subtypes. This is the only layer where raw network exceptions are handled.
27+
28+
2. **Repository Layer**: The `DataRepository` calls the data clients. It does not typically handle exceptions itself but allows them to propagate upwards. This keeps the repository focused on its role as a data aggregator.
29+
30+
3. **Business Logic Layer (BLoC)**: The BLoC is where exceptions are ultimately handled. A BLoC will wrap its calls to the repository in a `try/catch` block.
31+
- If an operation is successful, it emits a `Success` state with the fetched data.
32+
- If an `HttpException` is caught, it emits a `Failure` state, capturing the exception.
33+
34+
```dart title="lib/headlines-feed/bloc/headlines_feed_bloc.dart"
35+
// ...
36+
try {
37+
final headlineResponse = await _headlinesRepository.readAll(/* ... */);
38+
// ...
39+
emit(state.copyWith(status: HeadlinesFeedStatus.success, /* ... */));
40+
} on HttpException catch (e) {
41+
emit(state.copyWith(status: HeadlinesFeedStatus.failure, error: e));
42+
}
43+
// ...
44+
```
45+
46+
4. **Presentation Layer (UI)**: The UI uses a `BlocBuilder` to listen for state changes from the BLoC.
47+
- When it receives a `Success` state, it renders the data.
48+
- When it receives a `Failure` state, it displays a user-friendly error message using the shared `FailureStateWidget`. This widget takes the exception from the state and uses a helper method (`toFriendlyMessage`) to convert it into a localized, human-readable string.
49+
50+
This structured approach ensures that UI components are never directly responsible for error handling logic, leading to a cleaner and more maintainable codebase.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
title: Architecture Overview
3+
description: An overview of the mobile client's layered architecture.
4+
---
5+
import { Card, CardGrid } from '@astrojs/starlight/components';
6+
7+
The Flutter mobile client is built upon a clean, layered architecture designed for scalability, testability, and maintainability. This structure separates concerns, making the codebase easier to understand and extend.
8+
9+
The architecture is divided into four primary layers:
10+
11+
<CardGrid>
12+
<Card title="1. Data Layer" icon="data">
13+
This is the lowest layer, responsible for all communication with external data sources. It consists of **Data Client** implementations (e.g., `DataApi` for HTTP requests, `DataInMemory` for mock data). This layer handles the raw data fetching and maps external errors to a set of standardized exceptions.
14+
</Card>
15+
<Card title="2. Repository Layer" icon="repository">
16+
The Repository Layer abstracts the data sources from the rest of the application. It uses the Data Clients to perform CRUD operations but hides the implementation details. This layer provides a clean, consistent API for the Business Logic Layer to consume.
17+
</Card>
18+
<Card title="3. Business Logic Layer (BLoC)" icon="bolt">
19+
This layer contains the application's state and business logic, implemented using the **BLoC (Business Logic Component)** pattern. BLoCs respond to events from the UI, interact with repositories to fetch or update data, and emit new states for the UI to render.
20+
</Card>
21+
<Card title="4. Presentation Layer (UI)" icon="computer">
22+
The top layer, responsible for rendering the user interface. It is composed of Flutter widgets that listen to state changes from BLoCs and dispatch events based on user interaction. This layer contains no business logic; it is purely concerned with presentation.
23+
</Card>
24+
</CardGrid>
25+
26+
This layered approach ensures a unidirectional data flow, making the application's state predictable and easy to debug.

src/content/docs/mobile-client/architecture/routing.mdx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,33 @@ Navigation in the mobile client is managed by the powerful `go_router` package.
3030
The router is used to pass complex data between pages. For example, when navigating to a details page, the full `Headline` object is passed via the `extra` parameter of the navigation call. This is more efficient than passing an ID and re-fetching data that is already available.
3131
</Card>
3232
</CardGrid>
33+
34+
### Route Structure
35+
36+
The main application routes are structured using a `StatefulShellRoute`, which provides the bottom navigation bar. Top-level routes are used for pages that should appear over the main shell, such as authentication or detailed content views.
37+
38+
```tree title="Main Route Tree"
39+
/
40+
├─ /feed
41+
│ ├─ article/:id
42+
│ ├─ notifications
43+
│ └─ filter
44+
│ ├─ topics
45+
│ └─ sources
46+
├─ /search
47+
│ └─ article/:id
48+
└─ /account
49+
├─ settings
50+
│ ├─ appearance
51+
│ │ ├─ theme
52+
│ │ └─ font
53+
│ ├─ feed
54+
│ └─ language
55+
├─ manage-followed-items
56+
│ ├─ topics
57+
│ │ └─ add-topic
58+
│ └─ sources
59+
│ └─ add-source
60+
└─ saved-headlines
61+
└─ article/:id
62+
```
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
title: State Management (BLoC)
3+
description: An explanation of the BLoC pattern for state management in the mobile client.
4+
---
5+
import { Aside } from '@astrojs/starlight/components';
6+
7+
The mobile client uses the **BLoC (Business Logic Component)** pattern for state management. This pattern helps to separate the presentation layer from the business logic, making the application more structured, testable, and maintainable.
8+
9+
Every feature in the application that requires managing state has its own BLoC.
10+
11+
### The Core Components of BLoC
12+
13+
A BLoC is made up of three main parts:
14+
15+
1. **Events**: Events are the inputs to a BLoC. They are dispatched from the UI in response to user interactions (e.g., a button press) or lifecycle events (e.g., the page loading). Events are defined as simple classes that extend a base `Event` class.
16+
17+
2. **States**: States are the outputs of a BLoC. They represent a part of the application's state and are emitted by the BLoC in response to events. The UI listens to the stream of states and rebuilds itself to reflect the latest state. States are typically modeled as immutable classes.
18+
19+
3. **BLoC**: The BLoC itself is the component that sits between the UI and the data layer. It receives events, processes them (often by interacting with a repository), and emits new states.
20+
21+
### A Practical Example: `AccountBloc`
22+
23+
Let's look at the `AccountBloc`, which manages the state for the user's account screen.
24+
25+
- **Event**: When a user toggles following a topic, the UI dispatches an `AccountFollowTopicToggled` event, which contains the `Topic` object.
26+
27+
```dart title="lib/account/bloc/account_event.dart"
28+
class AccountFollowTopicToggled extends AccountEvent {
29+
const AccountFollowTopicToggled({required this.topic});
30+
final Topic topic;
31+
}
32+
```
33+
34+
- **BLoC Logic**: The `AccountBloc` listens for this event. In its event handler, it updates the user's preferences by calling the `_userContentPreferencesRepository` and then emits a new state.
35+
36+
```dart title="lib/account/bloc/account_bloc.dart"
37+
on<AccountFollowTopicToggled>(_onAccountFollowTopicToggled);
38+
39+
Future<void> _onAccountFollowTopicToggled(
40+
AccountFollowTopicToggled event,
41+
Emitter<AccountState> emit,
42+
) async {
43+
// ... logic to add/remove topic from preferences ...
44+
final updatedPrefs = // ...
45+
46+
// Persist the change
47+
await _userContentPreferencesRepository.update(item: updatedPrefs);
48+
49+
// Emit the new state
50+
emit(state.copyWith(status: AccountStatus.success, preferences: updatedPrefs));
51+
}
52+
```
53+
54+
- **State**: The `AccountState` holds the current user's `UserContentPreferences`. When the BLoC emits a new state with the updated preferences, the UI rebuilds.
55+
56+
```dart title="lib/account/bloc/account_state.dart"
57+
class AccountState extends Equatable {
58+
const AccountState({
59+
this.status = AccountStatus.initial,
60+
this.preferences,
61+
// ...
62+
});
63+
64+
final AccountStatus status;
65+
final UserContentPreferences? preferences;
66+
// ...
67+
}
68+
```
69+
70+
- **UI Integration**: The `FollowedTopicsListPage` uses a `BlocBuilder` to listen to the `AccountBloc`. When the state changes, it rebuilds the list of topics, and the UI reflects whether the topic is followed or not.
71+
72+
```dart title="lib/account/view/manage_followed_items/topics/followed_topics_list_page.dart"
73+
// ...
74+
body: BlocBuilder<AccountBloc, AccountState>(
75+
builder: (context, state) {
76+
// ... build UI based on state.preferences.followedTopics ...
77+
},
78+
),
79+
// ...
80+
```
81+
82+
This unidirectional data flow—from UI event to BLoC to new UI state—is the foundation of state management in the application.

src/content/docs/mobile-client/deployment.mdx

Lines changed: 35 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,98 +2,58 @@
22
title: 'Deployment: Mobile Client'
33
description: A guide to building and deploying the Flutter mobile app for Android and iOS.
44
---
5+
import { Steps, Aside } from '@astrojs/starlight/components';
56

6-
import { Steps, Tabs, TabItem, Aside } from '@astrojs/starlight/components';
7+
This guide provides a high-level overview of the tasks required to build and deploy the Flutter News App Mobile Client to the Google Play Store and Apple App Store.
78

8-
This guide covers the essential pre-release tasks for building and deploying the Flutter News App Mobile Client to the Google Play Store and Apple App Store.
9+
For detailed, step-by-step instructions, this guide will link to the official Flutter and platform documentation. These are the definitive resources and are always kept up-to-date.
910

10-
<Aside>
11-
Before you begin, ensure you have configured the app to connect to your production API server by setting the `appEnvironment` constant in `lib/main.dart` to `AppEnvironment.production`.
12-
</Aside>
11+
### Prerequisites
1312

14-
<Tabs>
15-
<TabItem label="Android">
16-
<Steps>
17-
1. **Update the Package ID**
18-
19-
The Android package ID is a unique identifier for your app. You must change it from the default.
20-
- Open `android/app/build.gradle`.
21-
- Locate the `applicationId` field and change its value to your own unique ID (e.g., `com.yourcompany.newsapp`).
22-
23-
2. **Change the App Display Name**
24-
25-
- Open `android/app/src/main/AndroidManifest.xml`.
26-
- Find the `android:label` attribute within the `<application>` tag and update its value to your app's name.
27-
28-
3. **Generate App Icons**
29-
30-
The project uses the `flutter_launcher_icons` package to generate app icons.
31-
- Replace the placeholder icon file in your project's `assets` folder with your own icon (`1024x1024` recommended).
32-
- Update the `flutter_launcher_icons.yaml` file if your icon has a different name.
33-
- Run the following command to generate the icons:
34-
```bash
35-
flutter pub run flutter_launcher_icons
36-
```
37-
38-
4. **Create a Production Keystore**
13+
Before you begin, you will need active developer accounts for the platforms you are targeting:
14+
- **Google Play Console:** [developer.android.com/distribute](https://developer.android.com/distribute)
15+
- **Apple Developer Program:** [developer.apple.com/programs](https://developer.apple.com/programs/)
3916

40-
You must sign your Android app with a private key before uploading it to the Play Store.
41-
- Follow the official Android documentation to [generate a new upload key and keystore](https://developer.android.com/studio/publish/app-signing#generate-key).
42-
- Securely store the generated `.jks` file and remember your passwords.
43-
44-
5. **Configure Gradle for Signing**
17+
<Steps>
18+
1. **Configure for Production**
4519

46-
- Create a file named `android/key.properties` (this file is in `.gitignore` and should not be committed).
47-
- Add your keystore information to this file:
48-
```properties
49-
storePassword=YOUR_STORE_PASSWORD
50-
keyPassword=YOUR_KEY_PASSWORD
51-
keyAlias=YOUR_KEY_ALIAS
52-
storeFile=path/to/your/keystore.jks
53-
```
54-
- Open `android/app/build.gradle` and ensure the signing configuration section is set up to read from this `key.properties` file.
20+
First, ensure the app is configured to connect to your live production API.
5521

56-
6. **Build the Release Bundle**
22+
- Open the file `lib/main.dart`.
23+
- Locate the `appEnvironment` constant.
24+
- Set its value to `AppEnvironment.production`.
5725

58-
Generate a production-ready Android App Bundle (`.aab`):
59-
```bash
60-
flutter build appbundle
26+
```dart title="lib/main.dart"
27+
// Use `AppEnvironment.production` to connect to a live backend API.
28+
const appEnvironment = AppEnvironment.production;
6129
```
62-
The output file will be located at `build/app/outputs/bundle/release/app-release.aab`. You can now upload this file to the Google Play Console.
63-
64-
</Steps>
65-
</TabItem>
66-
<TabItem label="iOS">
67-
<Steps>
68-
1. **Update the Bundle Identifier**
6930

70-
The iOS bundle identifier is a unique ID for your app.
71-
- Open the project in Xcode (`ios/Runner.xcworkspace`).
72-
- In the Project Navigator, select the `Runner` target.
73-
- Go to the "General" tab and update the "Bundle Identifier" field to your unique ID (e.g., `com.yourcompany.newsapp`).
31+
<Aside>
32+
Ensure that the `baseUrl` for the `production` environment in `lib/app/config/app_config.dart` points to the correct URL of your deployed [API Server](/docs/api-server/deployment/).
33+
</Aside>
7434

75-
2. **Update the Display Name**
35+
2. **Follow the Official Flutter Deployment Guides**
7636

77-
- In the same "General" tab in Xcode, update the "Display Name" field to your app's name.
37+
The Flutter team provides comprehensive guides that cover every aspect of building and releasing your application. Please follow the official guide for each platform you intend to deploy to.
7838

79-
3. **Generate the App Icon Set**
39+
#### **Android (Google Play Store)**
8040

81-
- In Xcode, navigate to `Runner/Assets.xcassets` and select `AppIcon`.
82-
- Drag and drop your prepared app icons (`.png` files of various required sizes) into the appropriate slots. You can use an online tool to generate these sizes from a single `1024x1024` image.
41+
The official guide covers essential steps including:
42+
- Updating the Package ID (`applicationId`).
43+
- Generating app icons.
44+
- Creating an upload keystore and configuring app signing.
45+
- Building the app bundle (`.aab`) for release.
8346

84-
4. **Configure Signing & Capabilities**
47+
**[Official Guide: Build and release an Android app](https://docs.flutter.dev/deployment/android)**
8548

86-
- In Xcode, go to the "Signing & Capabilities" tab.
87-
- Select your development team and ensure a provisioning profile and signing certificate are correctly configured. This is required for deploying to a real device or the App Store. You will need an active Apple Developer account.
49+
#### **iOS (Apple App Store)**
8850

89-
5. **Build the Release Archive**
51+
The official guide covers essential steps including:
52+
- Updating the Bundle Identifier and Display Name in Xcode.
53+
- Generating the app icon set.
54+
- Configuring app signing with your Apple Developer account.
55+
- Building the release archive (`.ipa`) for upload.
9056

91-
Create a build archive to upload to App Store Connect.
92-
```bash
93-
flutter build ipa
94-
```
95-
This command will create an `.ipa` file inside the `build/ios/ipa/` directory. You can then upload this archive to App Store Connect using Xcode's Organizer or the Transporter app for testing via TestFlight or submission to the App Store.
57+
**[Official Guide: Build and release an iOS app](https://docs.flutter.dev/deployment/ios)**
9658

9759
</Steps>
98-
</TabItem>
99-
</Tabs>

0 commit comments

Comments
 (0)