From 60e2854ff873c3edde5615e84e23bfd57c723320 Mon Sep 17 00:00:00 2001 From: vanethos Date: Sun, 5 May 2019 14:22:42 +0200 Subject: [PATCH 1/3] Add CONTRIBUTING.md and PROJECT_ARCHITECTURE.md --- CONTRIBUTING.md | 36 +++++++ PROJECT_ARCHITECTURE.md | 205 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 PROJECT_ARCHITECTURE.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..67ca0cf --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,36 @@ +# Contributing + +Everyone is welcome to contribute to the VOST application, including adding new screens, fixing current issues or suggesting enhancements. + +To do so, please read the following sections: +- [Opening an Issue](#opening-an-issue) +- [Creating A Pull Request](#creating-a-pull-request) +- [Style Guide](#style-guide) + +# Opening an Issue +## New Feature/Feature Request +A new feature should have the `enhanchement` label, with a title describing the new feature (“Add new endpoints for VOST - gas stations”). + +If the feature is an UI improvement or a new screen, please provide a wireframe of the screen, which can be made with apps such as [MockFlow WireframePro - Editor](https://wireframepro.mockflow.com/editor.jsp?editor=on&bgcolor=white&perm=Create&ptitle=Warranty%20Example&store=yes&category=M270f5278938129256fdb714d2bdd36cf1550137767039&projectid=Ma33dc569ed488dd5d67216b46ee7e0d71554926933286&publicid=aea96dad4cfb471b95fff6b1b2326b23&redirect=yes#/page/ab44169772fb4b4ca6a5e81f02d80e33). + +The issue message should explain what the feature is going to change in the app and possible conflicts (“The new endpoints will change the list item in the Home Screen”). + +## Issue or Bug +When filing a bug, please follow the [Github Issue Template Guideline](https://gist.github.com/auremoser/72803ba969d0e61ff070#file-issue_template-md) + +# Creating a Pull Request +When creating a Pull Request, please make sure your code follows the [Style Guide](#style-guide). + +This project follows the [GitFlow](https://datasift.github.io/gitflow/IntroducingGitFlow.html) structure in the git tree, so when creating a new feature or fixing an issue, please create the appropriate branch in the `development` branch. When your feature is finished, merge it back into `development` and create your pull request. + +Additionally, each commit message must follow this set of rules: +- Reference the issue that you are fixing/adding as a feature +- Explain what the commit does (“Fixes issue #1), “Adds a button to the bottom bar for ‘Home’”) +- Use the present tense (“Add feature” not “Added feature”) +- Use the imperative mood (“Move cursor to…” not “Moves cursor to…”) + +# Style Guide +When creating a pull-request to the VOST repo, you must: +- Format the code with `dartfmt` +- Comment any added Functions and Methods according to the [Official Dart Recommendations](https://www.dartlang.org/guides/language/effective-dart/documentation) for commenting code. +- Follow our [Project Architecture](https://github.com/FlutterPortugal/vost-app/blob/master/PROJECT_ARCHITECTURE.md) guidelines. \ No newline at end of file diff --git a/PROJECT_ARCHITECTURE.md b/PROJECT_ARCHITECTURE.md new file mode 100644 index 0000000..cdbc39b --- /dev/null +++ b/PROJECT_ARCHITECTURE.md @@ -0,0 +1,205 @@ +## Project Architecture + +The VOST Application is created with [Flutter](https://flutter.dev/) following the [BLoC](https://www.didierboelens.com/2018/08/reactive-programming---streams---bloc/) architecture combined with [Clean Architecture](http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) .The app is divided into 3 sections: *Data, Domain* and *UI*. Each layer is responsible only for the logic contained in it and doesn’t know how the preciding and following layers work. This ensures that if one of the layer has to change, eg.: the API changes the endpoints or an object changes part of its fields, minimum impact should be propagated to the remaining layers. + +* *Data* is responsible to retrieve information from the network and store/fetch data saved into the SharedPreferences +* *Domain* is responsible for manipulating the data received by the data layer. Furthermore, this is where part of the Business Logic of the application is located. +* *UI* is composed of two sub-layers: *BLoC*s and *Widgets*. Widgets are resposible for displaying UI to the user, and should not manipulate data directly. The data shown to the user should be stored and manipulated by a *BLoC*. In most cases, each widget has a BLoC, and if needed, they can access and manipulate other BLoCs. +Dependency Injection is handled by the DependencyProvider, which can be accessed by the Widgets via `DependencyProvider.of(context)`. + +### Data Layer +#### Network +All *API* calls are managed by [Dio](https://pub.dartlang.org/packages/dio) which can easily manage REST API calls and add headers and interceptors for every request. The following structure is used: +* *Endpoints*: we declare the *method* (POST, PUT, GET, DELETE), the *json string* and the *endpoint* that we are calling. +* *Services*: here we *map* the *Response Object* to a *Data Object* that can be used in our project. +* *Models*: *Data Models* that represent the json object sent from the server. They are dividided into *Response*and *Request* objects and *Serializers*, needed for the json serialization and deserialization. +* *Interceptors:* the interceptors used for the *requests*, *responsonses*,*errors* and respective *logging* + +#### Endpoints +This class’s sole purpose is to *declare* the REST API *endpoints* and its necessary options, such as the *body* as a JSON string and the necessary *headers*. +An endpoint can be declared as follow: +```dart +const todoEndpoint = "todos"; +const categoryQuery = "category"; + +Future getData(String id, String categoryId) { + return dio.get(“$todoEndpoint/$id”, queryParameters: {categoryQuery: categoryId}); +} +``` + +Each Endpoints class has a *dependency* to the *Dio* object created in DependencyProvider. +*Services* and to the *Parser* so that it can *serialize* and *deserialize* son objects. + +This class sole purpose is to *map* the *Response*given by the Endpoints class to a data model class and to *Serialize to json* the *Request* objects. Each function can be declared as following: + +```dart +Observable getData(String id, String categoryId) { + return Observable.fromFuture(_api.getData(accessId, categoryId) + .map((response) => + _parser.parseJsonObject(response.data, MockDataRemote.serializer)); +} +``` + + +#### Models +A data model is built with the [built_value](https://github.com/google/built_value.dart) and [build_runner](https://pub.dartlang.org/packages/build_runner) to generate *immutable* data class objects with a *builder* method. + +```dart +import ‘package:built_value/built_value.dart’; +import ‘package:built_value/serializer.dart’; +import ‘package:vost/data/remote/models/_base/parser.dart’; + +part ‘mock_data_remote.g.dart’; + +abstract class MockDataRemote implements Built, SerializedModel { + MockDataRemote._(); + + static Serializer get serializer => _$mockDataRemoteSerializer; + + int get userId; + int get id; + String get title; + bool get completed; + + factory MockDataRemote([updates(MockDataRemoteBuilder b)]) = _$MockDataRemote; +} +``` + +All classes have to follow the structure provided above. To help in the creation of this class, you can use the following [Live Template](https://medium.com/flutter-community/live-templates-or-how-to-spend-less-time-writing-boilerplate-code-on-flutter-with-intellij-7fb2f769f23) + +```dart +import ‘package:built_value/built_value.dart’; +import ‘package:built_value/serializer.dart’; +import 'package:vost/data/remote/models/_base/parser.dart'; + +part ‘$FILE_NAME$.g.dart’; + +abstract class $CLASS_NAME$ implements Built<$CLASS_NAME$, $CLASS_NAME$Builder>, SerializedModel<$CLASS_NAME$> { +$CLASS_NAME$._(); + +static Serializer<$CLASS_NAME$> get serializer => _$$$STATIC_CLASS_NAME$Serializer; + +$END$ + +factory $CLASS_NAME$([updates($CLASS_NAME$Builder b)]) = _$$$CLASS_NAME$; +} +``` + +Additionally, every class that needs to be *serialized* and *deserialized* needs to be added to the `serializers.dart` file in the `@SerializersFor()` function. + +#### Shared Preferences +This package is responsible for *adding* and *retrieving* *data* directly from the *Shared Preferences* using the [shared_prefs](https://pub.dartlang.org/packages/shared_preferences) plugin. +To add a new object to the shared preferences, the following must be added +```dart +String getPrivatePhoneKey() { + return _sharedPreferences.getString(_privatePhoneKey); +} + +Future savePrivatePhoneKey(String key) async { + return await _sharedPreferences.setString(_privatePhoneKey, key); +} +const _privatePhoneKey = “private_phone_key” +``` + +Always keep the *keys* at the *bottom of the file* so that we can easily check what are the objects we are storing in memory + +### UI +#### Blocs + +Each new bloc must extends the *BaseBloc*. This base class gives the common functionality of all blocs, such as error handling, displaying error message, showing loading messages and handling the dispose of the streams, sinks and subjects. + +To *communicate* with a bloc, we must add a *Sink* to receive either *data* or, if there is no data to add to the bloc, an *Event*. This sink is derived from the *RxDart Subject* that we are using. Furthermore, we must *add each subscribed Subject* to the *disposable* so that it can be disposed when the bloc is disposed. + +To *receive data* we must use a *Stream* in the *Bloc* and a *StreamBuilder* to display data in the UI. Adding data to the stream can be done using the `.add` method of the *RxDart Subject*. + +An example of a bloc that handles input and output is as follows: + +``` dart +class HomeBloc extends BaseBloc { + MockManager _mockManager; + + /// Event to fetch new data + var _fetchNewDataSubject = PublishSubject(); + + Sink get fetchNewDataSink => _fetchNewDataSubject.sink; + + /// Event to relay MockData information to the UI + var _mockDataSubject = BehaviorSubject(); + + Stream get mockDataStream => _mockDataSubject.stream; + + HomeBloc(this._mockManager) { + disposable.add(_fetchNewDataSubject.stream + .flatMap((_) => _mockManager.getMockData()) + .listen(_mockDataSubject.add, onError: (error) { + print(error); + handleOnError(genericErrorMessage); + })); + } +} +``` + +#### Screens + +If we want our screen to display data and error and loading messages, we must extend the *BasePage* and *BaseState* classes. These classes setup the *init* methods and wires up the *bloc*. To be able to use this class, we must have a bloc to be used in this page. + +In VOST we are using *routing*, so each new page must be declared in the `app.dart`file either in the *routes*, for routes that we are not passing new items, or in *onGenerateRoutes*. We use the *BuildContext* to get the desired bloc from the *DependencyProvider*: + +```dart +class VostApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + //... + routes: { + routeHome: (context) => HomePage(title: 'Vost App', bloc: DependencyProvider.of(context).getHomeBloc(),), + }, + ); + } +} +``` + +Finally, all navigation methods used in the app must all be in the `navigation.dart` file. When adding a new page, add a function with a name that clarifies where we are navigating to: + +```dart +Future navigateToHomePage(BuildContext context, String userId) { + return Navigator.of(context).pushReplacementNamed(routeHome, arguments: userId); +} +``` + + +### Assets + +When adding new assets to the app, add a new constant to the appropriate file in the *presentation/assets* folder. + +For example, if we are adding a new image, we edit (or create) an `images.dart` file in the *presentation/assets* folder with the following line: + +```dart +const assetImageIllustrationPhone = ‘assets/illustration_phone.png’; +``` + +When adding the file, it must be added in the */assets* folder, and the following must be added to the `pubspec.yaml` file if not present: + +```yaml +flutter: + assets: + - assets/ +``` + +### Dependency Injection +Each new dependency should be declared in the *DependencyProvider*. +- If the dependency is *not* required by a widget in the app, we initialise it in the *init* method. If this dependency is required by a bloc, we must declare a *class member variable* so that the bloc can be initialised. +- If the dependency *is required* by a widget in the app (for example a Bloc), we declare a *getter* for it using the following structure: + +```dart +HomeBloc _homeBloc; + +HomeBloc getHomeBloc({bool forceCreation = false }) { + if(_homeBloc == null || forceCreation) { + _homeBloc = HomeBloc(_cowManager); + } + return _homeBloc; +} +``` + From 4ffc192b06bed3988d26d7c5550807f15543c187 Mon Sep 17 00:00:00 2001 From: vanethos Date: Wed, 8 May 2019 20:10:53 +0200 Subject: [PATCH 2/3] Change in the CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67ca0cf..be5854a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,11 @@ When filing a bug, please follow the [Github Issue Template Guideline](https://g # Creating a Pull Request When creating a Pull Request, please make sure your code follows the [Style Guide](#style-guide). -This project follows the [GitFlow](https://datasift.github.io/gitflow/IntroducingGitFlow.html) structure in the git tree, so when creating a new feature or fixing an issue, please create the appropriate branch in the `development` branch. When your feature is finished, merge it back into `development` and create your pull request. +This project follows the [GitFlow](https://datasift.github.io/gitflow/IntroducingGitFlow.html) structure in the git tree, so when creating a new feature or fixing an issue, please create the appropriate branch from the `development` branch. + +The branching will use the branch-per-issue workflow: for each open issue, we'll create a corresponding git branch. + +For instance, issue #123 should have a corresponding feature/API-123-ShortTaskDescription branch, which MUST branch off the latest code in development. Additionally, each commit message must follow this set of rules: - Reference the issue that you are fixing/adding as a feature From c1c4863868299aac4f1eaefbad1cf9d57b30ffb1 Mon Sep 17 00:00:00 2001 From: vanethos Date: Wed, 8 May 2019 20:28:39 +0200 Subject: [PATCH 3/3] Add Project Details to CONTRIBUTING.md --- CONTRIBUTING.md | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be5854a..39b50b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,11 +2,45 @@ Everyone is welcome to contribute to the VOST application, including adding new screens, fixing current issues or suggesting enhancements. -To do so, please read the following sections: +For discussion about the project, please join our [Discord Server](https://discord.gg/4tX46B) and check the #vostpt channel. + +If you see any issue in the app, feel free to open a new issue, documenting it in accordance to [Opening an Issue](#opening-an-issue). + + + +- [Project Details](#project-details) - [Opening an Issue](#opening-an-issue) - [Creating A Pull Request](#creating-a-pull-request) - [Style Guide](#style-guide) +## Project Details + +### Setup + +This app is built with Flutter and Dart 2, follwing Bloc's design Pattern. + +### Run & Go + +``` +$ git clone https://github.com/FlutterPortugal/vost-app +``` + +Make sure you have installed Flutter following [these instructions](https://flutter.io/get-started/install/). + +``` +$ cd vost-app +$ flutter doctor +``` + +Fix anything `$ flutter doctor` asks. +Make sure you have a device connected (Simulator or Emulator or a real device connected) and then run + +``` +$ flutter run +``` + +You can set up [Visual Studio Code or IntelliJ IDEA/Android Studio](https://flutter.io/get-started/editor/) to do this for you. + # Opening an Issue ## New Feature/Feature Request A new feature should have the `enhanchement` label, with a title describing the new feature (“Add new endpoints for VOST - gas stations”). @@ -37,4 +71,4 @@ Additionally, each commit message must follow this set of rules: When creating a pull-request to the VOST repo, you must: - Format the code with `dartfmt` - Comment any added Functions and Methods according to the [Official Dart Recommendations](https://www.dartlang.org/guides/language/effective-dart/documentation) for commenting code. -- Follow our [Project Architecture](https://github.com/FlutterPortugal/vost-app/blob/master/PROJECT_ARCHITECTURE.md) guidelines. \ No newline at end of file +- Follow our [Project Architecture](https://github.com/FlutterPortugal/vost-app/blob/master/PROJECT_ARCHITECTURE.md) guidelines.