Skip to content

Refactor project with custom changes and enhancements: #56

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flutter-plugins-dependencies
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"_info":"// This is a generated file; do not edit or check into version control.","dependencyGraph":[{"name":"shared_preferences","dependencies":["shared_preferences_macos","shared_preferences_web"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]}]}
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"shared_preferences_foundation","path":"/Users/linkawy/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.0/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"shared_preferences_android","path":"/Users/linkawy/.pub-cache/hosted/pub.dev/shared_preferences_android-2.3.0/","native_build":true,"dependencies":[]}],"macos":[{"name":"shared_preferences_foundation","path":"/Users/linkawy/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.0/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/linkawy/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/linkawy/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.0/","native_build":false,"dependencies":["path_provider_linux"]}],"windows":[{"name":"path_provider_windows","path":"/Users/linkawy/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/linkawy/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.0/","native_build":false,"dependencies":["path_provider_windows"]}],"web":[{"name":"shared_preferences_web","path":"/Users/linkawy/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.1/","dependencies":[]}]},"dependencyGraph":[{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2024-10-06 12:51:25.711950","version":"3.22.2"}
106 changes: 95 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,104 @@

# TDD Clean Architecture for Flutter

### The whole accompanying tutorial series is available at :point_right: [this link](https://resocoder.com/flutter-clean-architecture-tdd/) :point_left:.
<p align="center">
<img src="assets/images/number_trivia_app.png" alt="Number Trivia App" width="300"/>
</p>
<p align="center">
<h3 align="center">Number Trivia App</h3>
<p align="center">
Number Trivia App using clean architecture and test-driven development
<br>
Project inspired by Reso Coder's <a href="https://resocoder.com/category/tutorials/flutter/tdd-clean-architecture/">Clean Architecture TDD Course</a>.
<br>
</p>
</p>

## What's Included

- :star: **State Management** using the BLoC pattern
- :star: **Test-Driven Development** with unit tests for domain and data layers using `mocktail`
- :star: **Dependency Injection** using `get_it`
- :star: **Functional Programming** with `dartz` package
- :star: **Equality Checks** in classes using `equatable`
- :star: **REST API** integration with `http`
- :star: **Exception Handling** using custom Failure classes
- :star: **Persistent Data** using shared preferences
- :star: **Null Safety** support for safer, more reliable code

## Project Architecture

This project follows the principles of **Clean Architecture**. The code is split into the following layers:

- **Domain Layer**: Contains core business logic and use cases.
- **Data Layer**: Handles data from APIs, local databases, or any external sources.
- **Presentation Layer**: Manages the user interface (UI) using BLoC for state management.

### Clean Architecture Diagrams

- **Reso Coder's Clean Architecture Diagram**
![Reso Coder's Clean Architecture](https://i.imgur.com/wqTYfpi.png)

- **Robert C. Martin's Clean Architecture Diagram**
![Robert C. Martin's Clean Architecture](https://i.imgur.com/6uzRuuN.jpg)

By using this structure, it's easier to test, modify, and extend the application, especially in large-scale projects.

## Key Features

### 1. **Bloc State Management**
Bloc (Business Logic Component) is used for managing states across the app. This makes the app's business logic independent of the UI.

### 2. **Dependency Injection**
The `get_it` package is used for easy dependency injection, making it simple to switch implementations or mock objects for testing.

### 3. **Functional Programming with Dartz**
The `dartz` package enables functional programming patterns, such as using `Either` for handling failures and successes, ensuring clean and maintainable code.

### 4. **API Integration**
The app uses the `http` package for fetching data from a REST API. It retrieves [describe the data, e.g., trivia, user information].

### 5. **Error Handling**
Custom `Failure` classes handle exceptions gracefully, ensuring a smooth user experience when errors occur.

### 6. **Null Safety**
This project is built with Dart's **Null Safety** features enabled, ensuring fewer runtime errors and safer code by handling null values properly.

## Testing

This project follows **Test-Driven Development (TDD)** practices. Unit tests are provided for the domain, data, and presentation layers. To run the tests, use:

```bash
flutter test
```

We use the `mocktail` package for mocking dependencies in the tests. This ensures clean and isolated testing of components by mocking external dependencies such as services and repositories.

## How to Contribute

Feel free to contribute to this project. You can do so by:

1. Forking the repository.
2. Creating a feature branch (`git checkout -b feature/your-feature`).
3. Committing your changes (`git commit -m 'Add some feature'`).
4. Pushing to the branch (`git push origin feature/your-feature`).
5. Opening a pull request.

---

### License

#### _Find more tutorials on [resocoder.com](https://resocoder.com)_
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

<br />
---

<h3 align="center">Architecture Proposal</h3>
**_More tutorials and information on Flutter development can be found on [Reso Coder](https://resocoder.com)._**

<br />
---

<img src="./architecture-proposal.png" style="display: block; margin-left: auto; margin-right: auto; width: 75%;"/>
This updated README includes information about:

<br />
<br />
- The use of `mocktail` for testing.
- Support for **Null Safety**, highlighting that the project uses Dart’s safety features to avoid null reference errors.

[![Reso Coder](https://resocoder.com/wp-content/uploads/2019/09/logo_with_text_signature.png)](https://resocoder.com)
<br />
_Be prepared for **real** app development_
If you'd like to add more details, like specific features or screenshots, feel free to reach out!
10 changes: 10 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
include: package:very_good_analysis/analysis_options.yaml

analyzer:
errors:
avoid_private_typedef_functions: ignore
cascade_invocations: ignore
inference_failure_on_instance_creation: ignore
one_member_abstracts: ignore


13 changes: 13 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java

# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks
65 changes: 28 additions & 37 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,67 +1,58 @@
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localPropertiesFile.withReader("UTF-8") { reader ->
localProperties.load(reader)
}
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
flutterVersionCode = '1'
flutterVersionCode = "1"
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
def flutterVersionName = localProperties.getProperty("flutter.versionName")
if (flutterVersionName == null) {
flutterVersionName = '1.0'
flutterVersionName = "1.0"
}

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 28
namespace = "com.example.clean_architecture_tdd_course"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}

lintOptions {
disable 'InvalidPackage'
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.resocoder.clean_architecture_tdd_course"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
applicationId = "com.example.clean_architecture_tdd_course"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}

buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
signingConfig = signingConfigs.debug
}
}
}

flutter {
source '../..'
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
source = "../.."
}
6 changes: 3 additions & 3 deletions android/app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.resocoder.clean_architecture_tdd_course">
<!-- Flutter needs it to communicate with the running application
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
Expand Down
46 changes: 30 additions & 16 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.resocoder.clean_architecture_tdd_course">

<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application
android:name="io.flutter.app.FlutterApplication"
android:label="clean_architecture_tdd_course"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in @style/LaunchTheme). -->
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.

In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.example.clean_architecture_tdd_course

import io.flutter.embedding.android.FlutterActivity

class MainActivity: FlutterActivity()

This file was deleted.

12 changes: 12 additions & 0 deletions android/app/src/main/res/drawable-v21/launch_background.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />

<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
18 changes: 18 additions & 0 deletions android/app/src/main/res/values-night/styles.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
14 changes: 12 additions & 2 deletions android/app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.

This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
6 changes: 3 additions & 3 deletions android/app/src/profile/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.resocoder.clean_architecture_tdd_course">
<!-- Flutter needs it to communicate with the running application
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
Expand Down
Loading