Skip to content

Commit

Permalink
Merge pull request #35 from nimblehq/release/0.4.0
Browse files Browse the repository at this point in the history
Release v0.4.0 (6)
  • Loading branch information
sleepylee authored Feb 11, 2022
2 parents 7d9b469 + f907b2f commit c0f6fe7
Show file tree
Hide file tree
Showing 23 changed files with 478 additions and 222 deletions.
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
SECRET=
REST_API_ENDPOINT=
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,10 @@ app.*.map.json

# Python
.venv/

# Flutter generated files
*.g.dart
*.gen.dart
*.config.dart
*.freezed.dart
*.mocks.dart
12 changes: 8 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ PYTHON3 := $(shell command -v python3 2> /dev/null)
VENV_ACTIVATE=$(VENV_NAME)/bin/activate
PYTHON=$(VENV_NAME)/bin/python3

PACKAGE_NAME=co.nimblehq.flutter.template
PROJECT_NAME=flutter_templates
APP_NAME=Flutter Templates
PACKAGE_NAME=
PROJECT_NAME=
APP_NAME=
APP_VERSION=0.1.0
BUILD_NUMBER=1

# Add the variable to env
export PACKAGE_NAME
export PROJECT_NAME
export APP_NAME
export APP_VERSION
export BUILD_NUMBER

.DEFAULT: help
help:
Expand All @@ -35,7 +39,7 @@ prepare-dev:
python3 -m venv $(VENV_NAME)

init: prepare-dev
$(PYTHON) ./scripts/setup.py --project_path $(PWD) --package_name $(PACKAGE_NAME) --project_name $(PROJECT_NAME) --app_name "$(APP_NAME)"
$(PYTHON) ./scripts/setup.py --project_path $(PWD) --package_name "$(PACKAGE_NAME)" --project_name "$(PROJECT_NAME)" --app_name "$(APP_NAME)" --app_version "$(APP_VERSION)" --build_number "$(BUILD_NUMBER)"

test: prepare-dev
$(PYTHON) ./scripts/test.py
Expand Down
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,35 @@ Clone the repository
`git clone [email protected]:nimblehq/flutter_templates.git`

## Prerequisite

- Flutter 2.2
- Flutter version manager (recommend): [fvm](https://fvm.app/)

## Getting Started

- Create these env files in the root directory according to the flavors and add the required environment variables into them. The example environment variable is in `.env.sample`.
### Setup

- Create these `.env` files in the root directory according to the flavors and add the required
environment variables into them. The example environment variable is in `.env.sample`.

- Staging: `.env.staging`

- Production: `.env`

- Run code generator

- `$ fvm flutter packages pub run build_runner build --delete-conflicting-outputs`

### Run

- Run the app with the desire app flavor:

- Staging: `$ fvm flutter run --flavor staging`

- Production: `$ fvm flutter run --flavor production`

### Test

- Run unit testing:

- `$ fvm flutter test .`
Expand All @@ -38,10 +50,6 @@ Clone the repository

`$ fvm flutter drive --driver=test_driver/integration_test.dart --target=integration_test/my_home_page_test.dart --flavor staging`

- Generate assets folder

- `$ fvm flutter packages pub run build_runner build --delete-conflicting-outputs`

## Use the template

### Setup a new project
Expand All @@ -54,7 +62,15 @@ Clone the repository

- Re-fetch the project: `$ fvm flutter pub get`

- The project uses the package name, the app name and the project name of the template if `PACKAGE_NAME`, `APP_NAME` and `PROJECT_NAME` aren't specified.
- Parameters detail:

| Parameter name | Is mandatory | Description |
| :------------- | :----------: | :------------------------------------------------------------------------------ |
| PACKAGE_NAME | Yes | The application package name. The naming convention follows `com.your.package` |
| PROJECT_NAME | Yes | The application project name. The naming convention follows `your_project_name` |
| APP_NAME | Yes | The application name. |
| APP_VERSION | No | The app version that is set when initialize the project. Default is `0.1.0` |
| BUILD_NUMBER | No | The build number that is set when initialize the project. Default is `1` |

- For more supporting commands, run:

Expand Down
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ project.ext.envConfigFiles = [
apply from: project(':flutter_config').projectDir.getPath() + "/dotenv.gradle"

android {
compileSdkVersion 30
compileSdkVersion 31

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
Expand All @@ -51,7 +51,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "co.nimblehq.flutter.template"
minSdkVersion 16
targetSdkVersion 30
targetSdkVersion 31
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
resValue "string", "build_config_package", "co.nimblehq.flutter.template"
Expand Down
8 changes: 4 additions & 4 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.6.10'
repositories {
google()
jcenter()
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.android.tools.build:gradle:4.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

Expand Down
11 changes: 10 additions & 1 deletion lib/api/api_service.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import 'package:dio/dio.dart';
import 'package:flutter_templates/model/response/user_response.dart';
import 'package:retrofit/retrofit.dart';

part 'api_service.g.dart';

@RestApi()
abstract class ApiService {
Future<UserResponse> getProfile();
factory ApiService(Dio dio, {String baseUrl}) = _ApiService;

// TODO add API endpoint
@GET('users')
Future<List<UserResponse>> getUsers();
}
162 changes: 162 additions & 0 deletions lib/api/exception/network_exceptions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'network_exceptions.freezed.dart';

@freezed
class NetworkExceptions with _$NetworkExceptions {
const factory NetworkExceptions.requestCancelled() = RequestCancelled;

const factory NetworkExceptions.unauthorisedRequest() = UnauthorisedRequest;

const factory NetworkExceptions.badRequest() = BadRequest;

const factory NetworkExceptions.notFound(String reason) = NotFound;

const factory NetworkExceptions.methodNotAllowed() = MethodNotAllowed;

const factory NetworkExceptions.notAcceptable() = NotAcceptable;

const factory NetworkExceptions.requestTimeout() = RequestTimeout;

const factory NetworkExceptions.receiveTimeout() = ReceiveTimeout;

const factory NetworkExceptions.sendTimeout() = SendTimeout;

const factory NetworkExceptions.conflict() = Conflict;

const factory NetworkExceptions.internalServerError() = InternalServerError;

const factory NetworkExceptions.notImplemented() = NotImplemented;

const factory NetworkExceptions.serviceUnavailable() = ServiceUnavailable;

const factory NetworkExceptions.noInternetConnection() = NoInternetConnection;

const factory NetworkExceptions.formatException() = FormatException;

const factory NetworkExceptions.unableToProcess() = UnableToProcess;

const factory NetworkExceptions.defaultError(String error) = DefaultError;

const factory NetworkExceptions.unexpectedError() = UnexpectedError;

static NetworkExceptions fromDioException(error) {
if (error is Exception) {
try {
NetworkExceptions networkExceptions;
if (error is DioError) {
switch (error.type) {
case DioErrorType.cancel:
networkExceptions = NetworkExceptions.requestCancelled();
break;
case DioErrorType.connectTimeout:
networkExceptions = NetworkExceptions.requestTimeout();
break;
case DioErrorType.other:
networkExceptions = NetworkExceptions.noInternetConnection();
break;
case DioErrorType.receiveTimeout:
networkExceptions = NetworkExceptions.receiveTimeout();
break;
case DioErrorType.sendTimeout:
networkExceptions = NetworkExceptions.sendTimeout();
break;
case DioErrorType.response:
switch (error.response?.statusCode) {
case 400:
networkExceptions = NetworkExceptions.badRequest();
break;
case 401:
networkExceptions = NetworkExceptions.unauthorisedRequest();
break;
case 403:
networkExceptions = NetworkExceptions.unauthorisedRequest();
break;
case 404:
networkExceptions = NetworkExceptions.notFound("Not found");
break;
case 409:
networkExceptions = NetworkExceptions.conflict();
break;
case 408:
networkExceptions = NetworkExceptions.requestTimeout();
break;
case 500:
networkExceptions = NetworkExceptions.internalServerError();
break;
case 503:
networkExceptions = NetworkExceptions.serviceUnavailable();
break;
default:
var responseCode = error.response?.statusCode;
networkExceptions = NetworkExceptions.defaultError(
"Received invalid status code: $responseCode",
);
}
break;
}
} else if (error is SocketException) {
networkExceptions = NetworkExceptions.noInternetConnection();
} else {
networkExceptions = NetworkExceptions.unexpectedError();
}
return networkExceptions;
} on FormatException catch (_) {
return NetworkExceptions.formatException();
} catch (_) {
return NetworkExceptions.unexpectedError();
}
} else {
if (error.toString().contains("is not a subtype of")) {
return NetworkExceptions.unableToProcess();
} else {
return NetworkExceptions.unexpectedError();
}
}
}

static String getErrorMessage(NetworkExceptions networkExceptions) {
var errorMessage = "";
networkExceptions.when(notImplemented: () {
errorMessage = "Not Implemented";
}, requestCancelled: () {
errorMessage = "Request Cancelled";
}, internalServerError: () {
errorMessage = "Internal Server Error";
}, notFound: (String reason) {
errorMessage = reason;
}, serviceUnavailable: () {
errorMessage = "Service unavailable";
}, methodNotAllowed: () {
errorMessage = "Method not allowed";
}, badRequest: () {
errorMessage = "Bad request";
}, unauthorisedRequest: () {
errorMessage = "Unauthorised request";
}, unexpectedError: () {
errorMessage = "Unexpected error occurred";
}, requestTimeout: () {
errorMessage = "Connection request timeout";
}, noInternetConnection: () {
errorMessage = "No internet connection";
}, conflict: () {
errorMessage = "Error due to a conflict";
}, sendTimeout: () {
errorMessage = "Send timeout in connection with API server";
}, receiveTimeout: () {
errorMessage = "Receive timeout in connection with API server";
}, unableToProcess: () {
errorMessage = "Unable to process the data";
}, defaultError: (String error) {
errorMessage = error;
}, formatException: () {
errorMessage = "Unexpected error occurred";
}, notAcceptable: () {
errorMessage = "Not acceptable";
});
return errorMessage;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import 'package:flutter_templates/api/api_service.dart';
import 'package:flutter_templates/api/exception/network_exceptions.dart';
import 'package:flutter_templates/model/response/user_response.dart';

abstract class CredentialRepository {
Future<UserResponse> getProfile();
Future<List<UserResponse>> getUsers();
}

class CredentialRepositoryImpl extends CredentialRepository {
Expand All @@ -11,7 +12,11 @@ class CredentialRepositoryImpl extends CredentialRepository {
CredentialRepositoryImpl(this._apiService);

@override
Future<UserResponse> getProfile() {
return _apiService.getProfile();
Future<List<UserResponse>> getUsers() async {
try {
return await _apiService.getUsers();
} catch (exception) {
throw NetworkExceptions.fromDioException(exception);
}
}
}
18 changes: 18 additions & 0 deletions lib/di/interceptor/app_interceptor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:dio/dio.dart';

class AppInterceptor extends Interceptor {
bool _requireAuthenticate;

AppInterceptor(this._requireAuthenticate);

@override
Future onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
if (_requireAuthenticate) {
// TODO header authorization here
// options.headers
// .putIfAbsent(HEADER_AUTHORIZATION, () => "");
}
return super.onRequest(options, handler);
}
}
Loading

0 comments on commit c0f6fe7

Please sign in to comment.