diff --git a/.DS_Store b/.DS_Store
deleted file mode 100644
index f4600ef..0000000
Binary files a/.DS_Store and /dev/null differ
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..c164b65
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,8 @@
+version: 2
+updates:
+- package-ecosystem: mix
+ directory: "/"
+ schedule:
+ interval: daily
+ time: "12:00"
+ timezone: Europe/London
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 150b35d..1033d02 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,34 +1,38 @@
-name: flutter_tests
+name: Build
on:
push:
- branches: [main]
+ branches: [ "main" ]
pull_request:
- branches: [main]
+ branches: [ "main" ]
jobs:
- tests:
- runs-on: ubuntu-latest
+ build:
+ runs-on: macos-latest
+ defaults:
+ run:
+ working-directory: ./demo_app
steps:
- - name: Checkout the code
- uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- - name: Install and set Flutter version
- uses: subosito/flutter-action@v2.8.0
+ # Installing Flutter because it's easier to generate .lcov files for test coverage
+ - name: Install Flutter
+ uses: subosito/flutter-action@v2
with:
- flutter-version: '3.0.5'
+ flutter-version: '3.3.8'
channel: 'stable'
- - name: Restore packages
+ - name: Install dependencies
run: flutter pub get
- - name: Analyze
- run: flutter analyze
-
+ # Your project will need to have tests in test/ and a dependency on
+ # package:test for this step to succeed. Note that Flutter projects will
+ # want to change this to 'flutter test'.
- name: Run tests
run: flutter test --coverage
- - name: Upload coverage to codecov
- run: curl -s https://codecov.io/bash
- shell: bash
\ No newline at end of file
+ - uses: codecov/codecov-action@v2
+ with:
+ files: coverage/lcov.info
+ verbose: true # optional (default = false)
diff --git a/.gitignore b/.gitignore
index d2bbec7..9981f52 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,5 @@ pubspec.lock
# If you don't generate documentation locally you can remove this line.
doc/api/
-.idea
\ No newline at end of file
+.idea
+.DS_Store
\ No newline at end of file
diff --git a/README.md b/README.md
index 7dadc87..6478a33 100644
--- a/README.md
+++ b/README.md
@@ -1,555 +1,3402 @@
-# learn-flutter
-https://flutter.dev/
-## What is Flutter?
+
+
+![Learn](https://user-images.githubusercontent.com/17494745/200544789-0b024c77-0d49-4702-8866-b69f61521033.png)
+
+Learn the **`Flutter`** basics to get up-and-running **fast**
+and build **awesome cross-platform applications**!
+
+
+
+
+- [What? ๐ก](#what-)
+- [Why? ๐คท](#why-)
+- [Who? ๐ค](#who-)
+ - [Mac Focussed? ๐](#mac-focussed-)
+ - ["_`iOS` users `spend` more than `double` on `subscriptions` compared to `Android` users_"](#ios-users-spend-more-than-double-on-subscriptions-compared-to-android-users)
+- [Install โฌ๏ธ](#install-๏ธ)
+ - [Mac: Homebrew ๐บ](#mac-homebrew-)
+ - [_Manual_ Install](#manual-install)
+ - [Installing Flutter SDK](#installing-flutter-sdk)
+ - [Install `XCode`](#install-xcode)
+ - [Install Android Studio](#install-android-studio)
+ - [Installing `Cocoapods`](#installing-cocoapods)
+ - [Adding plugins to Android Studio](#adding-plugins-to-android-studio)
+ - [Checking everything](#checking-everything)
+ - [Installing for Windows devices](#installing-for-windows-devices)
+- [Core Principles ๐ฃ](#core-principles-)
+ - [Widgets](#widgets)
+ - [Stateless widgets](#stateless-widgets)
+ - [Stateful widgets](#stateful-widgets)
+ - [Layout](#layout)
+ - [Assets](#assets)
+ - [Navigation and routing](#navigation-and-routing)
+ - [Networking](#networking)
+ - [Local databases](#local-databases)
+ - [SQLite](#sqlite)
+ - [1. Add the dependencies](#1-add-the-dependencies)
+ - [2. Define a model](#2-define-a-model)
+ - [3. Open connection to the database](#3-open-connection-to-the-database)
+ - [4. Creating table](#4-creating-table)
+ - [5. CRUD operations](#5-crud-operations)
+ - [ObjectBox](#objectbox)
+ - [State management](#state-management)
+ - [Dependency injection](#dependency-injection)
+- [Testing ๐งช](#testing-)
+ - [Unit testing](#unit-testing)
+ - [Mock testing](#mock-testing)
+ - [Integration testing](#integration-testing)
+- [App demo ๐ฑ](#app-demo-)
+ - [0. Setting up a new project](#0-setting-up-a-new-project)
+ - [1. Project structure](#1-project-structure)
+ - [2. Creating a list of todos](#2-creating-a-list-of-todos)
+ - [3. Adding interactivity](#3-adding-interactivity)
+ - [4. Adding navigation](#4-adding-navigation)
+ - [5. Finishing touches](#5-finishing-touches)
+ - [6. Testing!](#6-testing)
+ - [6.1 Unit testing](#61-unit-testing)
+ - [6.2 Widget testing](#62-widget-testing)
+ - [6.3 Test coverage](#63-test-coverage)
+- [Final remarks ๐](#final-remarks-)
+
+
+
+
+# What? ๐ก
+
+**`Flutter`** is an open-source framework created by Google
+for creating multi-platform, high-performance applications
+from a single codebase. It makes it easier for you to build
+user interfaces that works both on web and mobile devices.
+
+**`Flutter`** uses
+[`Dart`](https://github.com/dwyl/learn-dart),
+a general-purpose programming language created by **Google**.
+If you come from an object-oriented programming language
+like `Java`, `C#`,
+`Go` or `Javascript/Typescript`,
+you will feel right at home.
+
+# Why? ๐คท
+
+1. **`Flutter`** can be used to build cross platform **native** applications
+(Android, iOS, Desktop and Web) using the _same_ codebase.
+This significantly simplifies maintenance costs and dev headache
+when deploying for either Android or iOS devices.
+
+2. The `Dart` programming language used in `Flutter`
+ is object oriented and familiar to most developers.
+`Flutter` benefits immensely by leveraging `Dart`.
+Being a language optimized for UI and compiling to ARM
+& x64 machine code for mobile, desktop and backend,
+it offers amazing performance benchmarks.
+
+3. **Development times** are **_significantly_ faster**
+ than other cross-platform frameworks
+ thanks to stateful hot-reloading
+ and excellent virtual device support.
+If we close the application,
+when we open it again
+we can continue from where we stopped.
+
+4. `Flutter` has a **_complete_ design system**
+with a library of **Material UI widgets**
+included which speeds up the
+development process.
+
+5. `Flutter` is the fastest-growing mobile development platform
+and is wildly used in production worldwide.
+
+![fast-pace](https://user-images.githubusercontent.com/194400/84572723-e3b04800-ad93-11ea-85e2-19e9693e5a26.png)
+
+`Flutter` overtook React Native 2020 in Google searches,
+further showcasing the growing trend of `Flutter`:
+
+https://trends.google.com/trends/explore?date=today%205-y&q=flutter,react%20native
+
+![flutter-vs-react-native](https://user-images.githubusercontent.com/194400/202675546-b2bbdd8a-c4fb-4b97-9e7c-1997fdcf0905.png)
+
+
+# Who? ๐ค
+
+This repo is useful for anyone
+that is interested in mobile and web app development.
+For anyone that hasn't yet touched `Flutter`, this
+repo is a *great* place to start to get your computer
+ready for Flutter/Dart development, understand the
+**main concepts** and *guide* you to then create
+your very first `Flutter` app.
+
+
+## Mac Focussed? ๐
+
+While the _installation_ steps below
+include Mac-specific steps like `Homebrew` and `XCode`,
+this guide can still _easily_ be followed by people
+using Linux or Windows as their OS.
+
+The _reason_ we use **`Mac`** is simple:
+it's the _only_ way to ship apps for **`iOS`**.
+
+Like it or not, **`iPhone`** now has a
+**`50%` Market Share in the US**:
+[visualcapitalist.com/iphone-majority-us-smartphones](https://www.visualcapitalist.com/iphone-majority-us-smartphones/)
+
+![iphone-americas-top-smartphone](https://user-images.githubusercontent.com/194400/202679987-28743fa1-45c7-455b-a8b8-ca2f29567628.jpg)
+
+In Europe, **`iPhone`** ownership/use correlates strongly to wealth of the nation;
+[Monaco](https://en.wikipedia.org/wiki/Monaco#Economy)
+and
+[Norway](https://en.wikipedia.org/wiki/Norway#Economy)
+the two countries with the highest GDP/Capita top the table
+with
+[**`69.91%`**](https://www.reddit.com/r/MapPorn/comments/xx4gp6/percentage_of_iphone_users_in_europe/)
+and
+[**`68.89%`**](https://gs.statcounter.com/os-market-share/mobile/norway)
+respectively.
+[mezha.media/en/2022/10/10/percentage-of-iphone-users-in-different-european-countries](https://mezha.media/en/2022/10/10/percentage-of-iphone-users-in-different-european-countries/)
-Flutter in an open Source SDK for creating high-performance mobile apps for IOS and Android.
-The Flutter makes it easier for you to build user interfaces, while reducing the amount of code required to create and update your app.
+![europe-iphone-market-share](https://user-images.githubusercontent.com/194400/202684011-b58184e6-3501-42f2-ad63-cca14c8e828f.png)
-## Why use Flutter?
+Worldwide **`iPhone`** has a **`~30%` Market Share**:
+[gs.statcounter.com/os-market-share/mobile/worldwide](https://gs.statcounter.com/os-market-share/mobile/worldwide)
+Mostly because there are _many_ cheap Android devices
+that have flooded the market.
-- Flutter can be used to build cross platform native applications (Android, IOS, Desktop and Web) using the same codebase.
-- The Dart programming language used in Flutter is object oriented and familiar to most developers.
-- Development times are significantly faster than other cross-platform frameworks thanks to stateful hot-reloading and excellent virtual device support.
-- If we close the application when we open it again we can continue from where we were
-- Flutter has a _complete_ design system with a library of Material UI widgets included which speed up the development process.
+But by _far_ the most important fact/stat to pay attention
+from an Native Mobile App development perspective is:
+
+### "_`iOS` users `spend` more than `double` on `subscriptions` compared to `Android` users_"
-## Core Principles
+[phonearena.com/news/app-store-users-spend-more-than-double-google-play-users-subscriptions_id138692](https://www.phonearena.com/news/app-store-users-spend-more-than-double-google-play-users-subscriptions_id138692)
+
+So ... if you're building a
+[**`SaaS` product**](https://github.com/dwyl/product-roadmap#why-are-we-building-an-app),
+you should focus _most_ of your effort
+on perfecting the UI/UX on **`iPhone`**.
-Flutter is designed for the creation of 2D mobile applications.
+This is _why_ we use **`Mac`** computers
+for our **`Flutter`** dev work.
+So we can run **`XCode`**
+and test on **`iOS`** devices
+and pay our bills.
+We would _much_ rather use
+a fully Open Source Hardware/Software platform.
+e.g:
+[Framework](https://github.com/dwyl/hq/issues/565);
+We _love_ their
+[Mission](https://frame.work/about)
+
+
+# Install โฌ๏ธ
+
+## Mac: Homebrew ๐บ
+
+The easiest way to install **`Flutter`**
+on a Mac is using **`Homebrew`**:
+[brew.sh](https://brew.sh)
+After you've installed `brew`,
+you can install **`Flutter`**
+with the command:
+
+```sh
+brew install --cask flutter
+```
+
+You should see something similar to:
+
+```sh
+==> Downloading https://storage.googleapis.com/releases/stable/macos/flutter
+#################################################################### 100.0%
+==> Installing Cask flutter
+==> Linking Binary 'dart' to '/opt/homebrew/bin/dart'
+==> Linking Binary 'flutter' to '/opt/homebrew/bin/flutter'
+๐บ flutter was successfully installed!
+```
+
+
+
+## _Manual_ Install
+
+Installing Flutter might seem like a daunting task.
+But do not worry, we'll help you get your local environment
+running in no time! Since we are targeting web and mobile,
+there are a few tools and SDKs we ought to install first.
+
+These steps will be oriented to Mac/Unix devices but you should
+be able to follow if you have a Windows device. If you're ever stuck,
+don't be shy! Reach out to us and [open an issue](https://github.com/dwyl/learn-flutter/issues),
+we'll get back to you as fast as we can!
+
+## Installing Flutter SDK
+
+Head over to https://docs.flutter.dev/get-started/install,
+select your operating system and follow the instructions.
+
+In our case, we're going to download the SDK for
+our Mac. After downloading the SDK, you should extract
+the `.zip` contents to a wanted location
+(in our case, we extracted the folder to our `Home` - `cd ~`).
+
+Now, we ought to update our `PATH` variable so we can access
+the binary we just downloaded to our command line. Open your terminal and:
+
+```sh
+cd $HOME
+nano .zshrc
+```
+
+And add `export PATH="$PATH:`pwd`/flutter/bin"` pointing
+to the location where you extracted the folder.
+Now, if you restart the terminal and type `flutter doctor`,
+you should be able to run the command with no problems.
+
+`flutter doctor` checks your environment and displays a report to the
+terminal window. It checks it all the necessary tools for development
+for all devices are correctly installed. Let's do just that.
+
+
+## Install `XCode`
+
+If you don't already have **`XCode`** installed,
+open your **`AppStore`**, search for `"XCode"`
+and press `Install`. It's that easy.
+
+
+
+### Install Android Studio
+Now targetting for Android devices, we need to install Android SDK and toolkits.
+For this, we are going to install Android Studio and work from there.
+Head over to https://developer.android.com/studio and download.
+
+After downloading, run the installer and select `Default settings` and let
+the installer do its magic. After this, you should be prompted with the following window.
+
+
+
+Click on the `More actions` dropdown and click on `SDK Manager`.
+You should be prompted with this window:
+
+
+
+After installing with default settings, you probably already have
+an Android SDK installed. If that's the case, follow through
+to `SDK Tools` and check on `Android SDK Command-line Tools`.
+
+
+
+And then click `Finish`. This will install the command line tools.
+
+After installing, copy the `Android SDK Location` in the window.
+Open a terminal window and type the following to add the SDK path
+to the `Path` env variable.
+
+```sh
+cd $HOME
+nano .zshrc
+```
+
+and then add the SDK path you just copied, and save the file
+
+`export ANDROID_HOME=PATH_YOU_JUST_COPIED`
+
+Restart your terminal again and type `flutter doctor --android-licenses`.
+This will prompt you to accept the Android licenses. Just type `y` as you read
+through them to accept.
+
+## Installing `Cocoapods`
+
+If you run `flutter doctor` again, you should see we are almost done.
+You might see a text saying `CocoaPods not installed`. Let's fix that.
+
+Install [Homebrew](https://brew.sh/) and run `brew install cocoapods`.
+
+And you should be all sorted!
+
+## Adding plugins to Android Studio
+
+If you happen to use Android Studio when developing,
+adding the Flutter plugin will help you tremendously.
+Just open Android Studio, click on `Plugins`,
+search for "Flutter" and click `Install`.
+
+
+
+You are asked to "Restart the IDE". Do so and ta-da :tada:, you are done!
+
+## Checking everything
+If you run `flutter doctor`, you should have everything in the green.
+
+
+
+Congratulations, give yourself a pat on the back, you are **all ready**!
+
+
+### Installing for Windows devices
+
+Installing Flutter on Windows should be the same process
+as the one on MacOS'. However, setting up the `$PATH`
+variable is different, since some terminal commands
+can't be used in Windows since it's not an Unix-based
+operating system.
+
+With this in mind, here's a quick rundown of how things
+should go if you're installing Flutter on Windows.
+Firstly, head over to https://docs.flutter.dev/get-started/install/windows
+and download the `.zip` file.
+
+![download](https://i.imgur.com/ZNPFKsl.png)
+
+Extract the file and place the folder in directory `C:`.
+It's probably best to create a folder in the directory like this.
+
+
+![cdrive](https://i.imgur.com/76IAhtp.png)
+
+This is the console that comes with the Flutter folder
+you just downloaded. You can see the devices connected or
+even create a project through here.
+
+![flutterconsole](https://i.imgur.com/rJe2Uao.png)
+
+In order to access Flutter commands through the terminal,
+instead of having to open this console, we need to update
+our environment variables.
+
+You need to go to the bin folder of the extracted
+`.zip` you downloaded and pasted on the `C:` drive
+and copy the path.
+Then, go to the computer properties, then go to advanced system settings.
+
+![properties](https://i.imgur.com/tKZP0ZG.png)
+
+Click on environment variables,
+go to edit path and paste the path to the extracted
+Flutter folder.
+
+![properties2](https://i.imgur.com/OoUtlWO.png)
+![properties3](https://i.imgur.com/1IvNuGT.png)
+
+
+As you can see, if you open a new Windows terminal
+(also known as `windows prompt`) and
+run the `flutter` command, this should prop up.
+
+ ![run_command](https://i.imgur.com/oSCrjRM.png)
+
+The rest of the steps should be straight forward.
+Just follow the ones on the Mac device.
+Installing Android Studio is the exact same procedure
+:smile:.
+
+# Core Principles ๐ฃ
+
+If you have had experience in mobile development prior to Flutter,
+either be it React Native or native, you will find the learning curve
+quite manageable, as Flutter's foundation is built upon a few principles
+that are present in both. Let's take a look at these :smile:.
+
+If you want an in-depth guide and learn every aspect of Flutter,
+check the official documentation -> https://flutter.dev/learn
## Widgets
-In Flutter _everything_ is a "Widget".
-A Widget is a UI building block you can use to assemble your app.
-In the following Gif the sample application contains a total of 6 widgets:
-![flutter-counter-sample](https://user-images.githubusercontent.com/194400/74101695-87fb5700-4b34-11ea-9fbd-09cc6bf3ed41.gif)
+In Flutter _everything_ is a **Widget**.
+
+A Widget is a UI building block you can use to assemble your app.
+If you come from `React`, you may find these akin to `components`.
+
+You will build your UI out of widgets. They essentially describe
+what *their view should look like* given their current state.
+If their state changes, the widget rebuilds and checks the diff
+to determine the minimal changes to transition from the state `t0` to `t1`.
+
+
+In the following `.gif` the sample application contains a total of 6 widgets:
+
+![widget-gif](https://user-images.githubusercontent.com/194400/74101695-87fb5700-4b34-11ea-9fbd-09cc6bf3ed41.gif)
+
Image attribution: https://uxplanet.org/why-you-should-use-google-flutter-42f2c6ba036c
-1. The **container** widget starting on line 17 groups all other widgets in the layout.
+
+1. The **container** widget `Scaffold` starting on line 38 groups all other widgets in the layout.
2. The ***`appBar`*** widget displays the text "Flutter Demo Home Page"
3. The ***`body`*** contains a **child** widget which in turn has **Text** and a **$_counter** placeholder.
-4. The **`floatingActionButton`** is the button that gets clicked, it contains a **child** which is the icon.
-Examples of Widgets include dialog windows, buttons, icons, menus, scroll bars and cards.
-You can use one of the many built-in Material UI widgets or create your own from scratch.
+4. The ***`floatingActionButton`*** is the button that gets clicked,
+it contains a **child** which is the icon.
+Examples of Widgets include
+dialog windows, buttons, icons, menus, scroll bars and cards.
+You can use one of the many built-in Material UI widgets
+or create your own from scratch.
A widget can be defined as:
- Physical elements of an application (buttons, menus or bars)
- Visual elements such as colors
-+ Layout and positioning of elements on the screen using a grid system
+- Layout and positioning of elements on the screen using a grid system
+
+Widgets are assembled in declarative hierarchy
+which allows us to easily organize
+the layout of our App as a series of nested widgets.
+
+![system](https://user-images.githubusercontent.com/27420533/73969408-4475d280-4913-11ea-8384-99c863321155.png)
+
+Screens are composed of several small widgets that have only one job.
+Groups of widgets are assembled together to build a functional application.
+
+For example, a Container widget contains other widgets
+ that have functions like layout, placement and size.
+
+A basic screen layout is controlled by combining
+a container and other smaller widgets as their children.
+This was seen in the gif above. The `Scaffhold` widget
+warps three widgets.
+
+Remember, Widgets aren't necessarily visual elements within the application.
+In the gif above, the second child `body` widget also uses
+a widget named `Center` that, as the name implies, centers
+its children within the screen. It's *controlling* the
+aspects of their child and displaying them centered.
+There are several other widgets that have a similar behaviour,
+such as padding, alignment, row, columns, and grids.
+
+### Stateless widgets
+
+Widgets are not all stateless. Stateless widgets never change.
+They receive arguments from their parent, store them in `final` member variables
+(`final` is analogous to a `const`ant variable). When a widget is asked
+to `build()`. it uses these stored values and renders.
+Here's what a stateless widget looks like:
+
+```dart
+class MyAppBar extends StatelessWidget {
+ const MyAppBar({required this.title, super.key});
+
+ // Fields in a Widget subclass are always marked "final".
+
+ final Widget title;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ height: 56.0, // in logical pixels
+ padding: const EdgeInsets.symmetric(horizontal: 8.0),
+ decoration: BoxDecoration(color: Colors.blue[500]),
+ // Row is a horizontal, linear layout.
+ child: Row(
+ children: [
+ const IconButton(
+ icon: Icon(Icons.menu),
+ tooltip: 'Navigation menu',
+ onPressed: null, // null disables the button
+ ),
+ // Expanded expands its child
+ // to fill the available space.
+ Expanded(
+ child: title,
+ ),
+ const IconButton(
+ icon: Icon(Icons.search),
+ tooltip: 'Search',
+ onPressed: null,
+ ),
+ ],
+ ),
+ );
+ }
+}
+```
-Widgets are assembled in declarative hierarchy which allows us to easily organise the layout of our App as a series of nested widgets.
+We notice straight away the widget is a subclass of
+[`StatelessWidget`](https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html).
+
+All widgets have a `Key key` (`super.key`)
+as an optional parameter in their constructor.
+The `key` is used by the **`Flutter` engine**
+at the step of recognizing which widget
+in a list has changed.
+It's more useful when you have a list of widgets
+*of the same type*
+that can potentially be removed or inserted.
+
+This `MyAppBar` widget takes as argument a `title`.
+This effectively becomes the `field` of the widget,
+and is used in the `Expanded` children widget.
+Additionally, since this is a widget (more specifically,
+a subclass of `Stateless Widget`), we have to
+implement the `build()` function.
+This is what is rendered.
+
+This widget could be used in a container
+and be one of its children
+like so:
+
+```dart
+ MyAppBar(
+ title: Text(
+ 'Example title',
+ ),
+ ),
+```
-![Screen Shot 2020-02-06 at 19 01 17](https://user-images.githubusercontent.com/27420533/73969408-4475d280-4913-11ea-8384-99c863321155.png)
+Simple enough, right?
+Here the `MyAppBar` is the parent widget,
+`title` is a property
+and `Text` is the child widget.
+
+### Stateful widgets
+
+While stateless widgets are static (never change),
+**stateful widgets** are dynamic.
+For example: they change appearance or behavior
+according to events triggered by user interaction
+or when it receives data.
+
+For example:
+`Checkbox`, `Slider`, `Textfield`
+are examples of
+[`StatefulWidget`](https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html).
+A widget's state is stored in a `State` object.
+Therefore,
+we _separate_ the widget's state
+from its appearance.
+Whenever the state changes,
+the `State` object calls `setState()`,
+thus rerendering the widget.
+
+Let's see some code!
+
+```dart
+import 'package:flutter/material.dart';
+
+class Counter extends StatefulWidget {
+ // Counter is the Stateful Widget, different from the appearance.
+ // It holds the state configuration and values
+ // provided by the parent and used by the build method
+ // of the State (no values are provided in this instance)
+ // Fields in a Widget subclass are always marked
+
+ const Counter({super.key});
+
+ @override
+ State createState() => _CounterState();
+}
-Screens are composed of several small widgets that have only one job. Groups of widgets are assembled together to build a functional application.
-For example a Container widget contains other widgets that have functions like layout, placement and size.
+class _CounterState extends State {
+ int _counter = 0;
+
+ void _increment() {
+ setState(() {
+ // This call to setState tells the Flutter framework
+ // that something has changed in this State, which
+ // causes it to rerun the build method below so that
+ // the display can reflect the updated values. If you
+ // change _counter without calling setState(), then
+ // the build method won't be called again, and so
+ // nothing would appear to happen.
+ _counter++;
+ });
+ }
-Screen layout is controlled by combining a container and other smaller widgets.
+ @override
+ Widget build(BuildContext context) {
+ // This method is rerun every time setState is called,
+ // for instance, as done by the _increment method above.
+ // The Flutter framework has been optimized to make
+ // rerunning build methods fast, so that you can just
+ // rebuild anything that needs updating rather than
+ // having to individually changes instances of widgets.
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ ElevatedButton(
+ onPressed: _increment,
+ child: const Text('Increment'),
+ ),
+ const SizedBox(width: 16),
+ Text('Count: $_counter'),
+ ],
+ );
+ }
+}
-There are some widgets that have no physical form within the application instead their goal and control some aspects of another Widget.
-Like: padding, alignment, row, columns, and grids.
+void main() {
+ runApp(
+ const MaterialApp(
+ home: Scaffold(
+ body: Center(
+ child: Counter(),
+ ),
+ ),
+ ),
+ );
+}
+```
-## Layers
+Let's unpack this.
+The `StatefulWidget` and `State` are separate objects.
+The former (being the first one)
+declares its state
+by using the `State` object.
+The `State` object is declared right after,
+initializing an `int _counter` at `0`.
+It declares an `_increment()` function
+that calls `setState()`
+(indicating the state is going to be changed)
+and increments the `_counter` variable.
+
+As with any widget,
+the `build()` method makes use of the `_counter` variable
+to display the number of times the button is pressed.
+Everytime it is pressed,
+the `_increment()` function is called,
+effectively changing the state and incrementing it.
+
+## Layout
+
+As we've already stated,
+the core of `Flutter` are widgets.
+In fact, almost everything is a widget -
+even layout models.
+The things you see are widgets.
+
+![image](https://user-images.githubusercontent.com/17494745/200579851-de25d19d-5c80-4033-8491-c2ff452f7137.png)
+
+But things that you *don't see* are also widgets.
+We mentioned this before
+but we'll understand it better now.
+For any web or mobile app development,
+we need to create layouts to organize our components
+and make them look _shiny_ โจ
+and _good-looking_ ๐จ.
+
+This example is taken from the official docs:
+https://docs.flutter.dev/development/ui/layout#lay-out-a-widget
+
+Layout | Layout with padding and delimited borders
+:-------------------------:|:-------------------------:
+![](https://docs.flutter.dev/assets/images/docs/ui/layout/lakes-icons.png) | ![](https://docs.flutter.dev/assets/images/docs/ui/layout/lakes-icons-visual.png)
+
+
+So, you may ask,
+**how many widgets are there in this menu**?
+Great question!
+
+In the pictures above,
+there are *visible* widgets
+but also widgets that *help us* lay out the items correctly,
+center them
+and space them evenly to make
+everything look good.
+These widgets **are not visible**.
+
+Here's how the widget tree looks like for this menu:
+
+![widget_tree](https://docs.flutter.dev/assets/images/docs/ui/layout/sample-flutter-layout.png)
+
+The **pink nodes** are **containers**.
+They are **_not_ visible**
+but help us **customize**
+their **child widget**
+by adding
+`padding`, `margin`, `border`, `background color`, etc...
+
+Let's see a code example of an invisible widget
+that will center a block of `text`
+in the middle of the screen:
+
+```dart
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: const BoxDecoration(color: Colors.white),
+ child: const Center(
+ child: Text(
+ 'Hello World',
+ textDirection: TextDirection.ltr,
+ style: TextStyle(
+ fontSize: 32,
+ color: Colors.black87,
+ ),
+ ),
+ ),
+ );
+ }
+}
+```
-A Flutter layout can have layers to create a visual effect where certain widgets "float" on top of others to give them priority.
+The `Center` widget centers all its children inside of it.
+`Center` is *invisible*
+but is a widget nonetheless.
+This yields the following result:
+![centered-text](https://user-images.githubusercontent.com/194400/203043079-7c9be65a-0b2b-4580-9dac-15f42ef3fb25.png)
-![Screen Shot 2020-02-07 at 09 06 06](https://user-images.githubusercontent.com/27420533/74015797-36629900-4989-11ea-8ec1-757aecad18ce.png)
+See? Isn't it so simple? ๐
-Flutter uses layers to represent visual hierarchy
-and relative importance or priority of each widget.
+You can create _any_ Layout you wish just
+by encapsulating widgets
+and ordering them accordingly.
-## Building Widgets
+## Assets
-To assemble our widgets into an application we use the `build()` function. e.g:
-`Widget build(BuildContext context) { ...`
+Sometimes,
+we need images and assets to be displayed in our App.
+Common resources are:
+image files,
+static data (`JSON` files),
+videos, buttons and icons.
-For example, the `appBar` menu
-needs to be invoked using the **`build()`** function.
-It can then have other nested "child" widgets
-such as buttons or text.
+In Flutter,
+we use a `pubspec.yaml` file
+(often located at the root of the project)
+to require assets into our app.
-## User Interaction
+```yaml
+flutter:
+ assets:
+ - directory/
+ - assets/my_icon.png
+```
-A **`StatefulWidget`** widget, as its' name suggests, stores the **`state`** of the UI it represents.
-For example the **`counter`** widget stores the **_counter** variable
-which keeps track of the number of times the user has clicked the button.
-All user interaction that requires storing some data/input uses a **`StatefulWidget`** widget.
+> There's a nuanced behavior when loading assets.
+>
+> If you have two files
+> ` .../graphics/background.png`
+> and `.../graphics/dark/background.png`
+> and the `pubspec.yaml` file
+> contains the following:
+
+> ```yaml
+> flutter:
+> assets:
+> - graphics/background.png
+> ```
+
+> Both are imported and included in the asset bundle.
+> One is considered the **main asset**
+> and the other a **variant**.
+> This behavior is useful for images of different resolutions.
+
+There are two ways of accessing the loaded assets.
+Each `Flutter` app has a `RootBundle`
+for easy access to the main asset bundle.
+You can import directly
+using the `rootBundle` global static.
+
+However, inside a widget context,
+it's recommended to obtain the asset bundle
+for the widget `BuildContext` using the
+[`DefaultAssetBundle`](https://api.flutter.dev/flutter/widgets/DefaultAssetBundle-class.html).
+This approach allows the parent widget
+to substitute a different asset bundle at runtime,
+which is useful for localization
+or testing purposes.
+
+Here's a code example for the `rootBundle` approach:
+
+```dart
+import 'package:flutter/services.dart' show rootBundle;
+
+Future loadAsset() async {
+ return await rootBundle.loadString('assets/config.json');
+}
+```
+Here's a code example
+for the recommended approach
+inside a widget:
-When the State of an object is changed, the `setState()` function
-should be called to update the UI.
-This in turn will invoke the `build` method
-which re-renders the widget.
+```dart
+String data = await DefaultAssetBundle.of(context).loadString("assets/data.json");
+final jsonResult = jsonDecode(data);
+```
-## Testing
+## Navigation and routing
+
+Most web and mobile apps aren't just a single page.
+The person using the app
+needs to navigate between screens
+to do whatever action needs to be done,
+be it checking the details of a product
+or just wanting to see the shopping cart.
+
+`Flutter` provides a `Navigator` widget
+to display screens as a **stack**
+using the native transition animations of the target device.
+Navigating between screens
+necessitates the route's `BuildContext`
+(which can be accessed through the widget)
+and is made by calling methods like
+`push()` and `pop()`.
+
+Here's code showcasing
+the navigation between two routes:
+
+```dart
+import 'package:flutter/material.dart';
+
+void main() {
+ runApp(const MaterialApp(
+ title: 'Navigation Basics',
+ home: FirstRoute(),
+ ));
+}
-As in all programming languages, frameworks or platforms the secret to a successful Flutter application is to test it _extensively_.
-Several tests should created included functional tests and UX tests
-which can help us discover and fix any bugs before users see them!
+class FirstRoute extends StatelessWidget {
+ const FirstRoute({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('First Route'),
+ ),
+ body: Center(
+ child: ElevatedButton(
+ child: const Text('Open route'),
+ onPressed: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => const SecondRoute()),
+ );
+ },
+ ),
+ ),
+ );
+ }
+}
-An application must have tested all functions, classes or tasks needed to run correctly without errors.
+class SecondRoute extends StatelessWidget {
+ const SecondRoute({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Second Route'),
+ ),
+ body: Center(
+ child: ElevatedButton(
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ child: const Text('Go back!'),
+ ),
+ ),
+ );
+ }
+}
+```
-Widget tests are necessary to confirm that they are performing their intended function
-and correctly positioned in the layout.
-Integration tests serve to test the application as a whole so that we can test the app in a real-world scenario.
+This basic code example showcases two routes,
+each one containing only a single button.
+Tapping the one on the first route
+will navigate to the second route.
+Clicking on the button of the second route
+will return the user to the first route.
+We are using
+the `Navigator.push()`
+and `Navigator.pop()` functions to achieve this,
+by passing the context of the widget.
+Additionally,
+we are leveraging `MaterialPageRoute`
+to transition between routes
+using a platform-specific animation
+according to the [Material Design guidelines](https://m3.material.io/).
+
+Here's how it should look!
+
+![navigating_gif](https://user-images.githubusercontent.com/17494745/200613079-f65baeee-a822-4a58-b075-ce169d751325.gif)
+
+
+If your application needs advanced navigation
+and routing requirements
+(which is often the case with web apps that use direct links to each screen,
+or an app with multiple `Navigator` widgets),
+you should consider using a
+routing package like [`go_router`](https://pub.dev/packages/go_router).
+This package allows one to parse the route path
+and configure the `Navigator` whenever an app receives,
+for example, a deep link.
+
+
+## Networking
+
+For most apps,
+fetching data from the internet is a must.
+Luckily,
+fetching data from the internet is a breeze.
+Let's do it!
+
+Firstly,
+we need to add the [`http`](https://pub.dev/packages/http) package
+to the dependencies section
+in the `pubspec.yaml` file.
+This file can be found
+at the route of your project.
+
+Let's add the package to the dependency list
+and import it.
+
+```yaml
+dependencies:
+ http: 0.13.5
+```
-> To learn more about an example of using TDD: https://github.com/dwyl/flutter-counter-example
+```dart
+import 'package:http/http.dart' as http;
+```
-## Dart Before Flutter?
-Before you dive into Flutter you have to learn the programming language that is used to build Flutter apps, and that is Dart.
+We also need to change the `AndroidManifest.xml` file
+to add Internet permission on Android devices.
+This file can be found in the `android/app/src/main`
+on newly created projects.
+Add the following line.
+```xml
+
+
+```
-Flutter for Windows installation
-==========
-Access the link and click Get Started
-![Flutter](https://i.imgur.com/35POvJA.png)
+Now,
+to make a network request
+is as easy as apple pie.
+Check the following code.
-Select the windows button and then click the "flutter_windows.zip" button.
+```dart
+Future fetchAlbum() {
+ return http.get(Uri.parse('https://jsonplaceholder.typicode.com/todos/1'));
+}
+```
+By calling `http.get()`,
+it returns a [`Future`](https://github.com/dwyl/learn-dart#asynchronous-events)
+that contains a `Response`.
+`Future` is a class to work with async operations.
+It represents a potential value that will occur in the future.
+
+While `http.Response` has our data,
+it's much more useful to translate it to a logical class.
+We can convert `http.Response`
+to a `Todo` class, representing a "todo item".
+Let's create that class!
+
+```dart
+class Todo {
+ final int id;
+ final String title;
+ final bool completed
+
+ const Todo({
+ required this.id,
+ required this.title,
+ required this.completed
+ });
-![Flutter](https://i.imgur.com/ZNPFKsl.png)
+ factory Todo.fromJson(Map json) {
+ return Todo(
+ id: json['id'],
+ title: json['title'],
+ completed: json['completed'],
+ );
+ }
+}
+```
-Extract the file and place the folder in directory "C:"
-It's probably best to create a folder in the directory like this.
+We can create a function that makes the HTTP request and,
+if it is successful,
+tries to parse the data and create a `Todo` object
+or raise an an error
+if the HTTP request is unsuccessful.
+
+```dart
+Future fetchTodos() async {
+ final response = await http
+ .get(Uri.parse('https://jsonplaceholder.typicode.com/todos/1'));
+
+ if (response.statusCode == 200) {
+ // If the server did return a 200 OK response,
+ // then parse the JSON.
+ return Todo.fromJson(jsonDecode(response.body));
+ } else {
+ // If the server did not return a 200 OK response,
+ // then throw an exception.
+ throw Exception('Failed to load todos');
+ }
+}
+```
-![Flutter](https://i.imgur.com/76IAhtp.png)
+How would we call this inside a widget?
+We could do this inside `initState()`!
+It is called exactly one time and never again!
+Do **not** put an API call in the `build()` method
+(unless you know what you are doing).
+This method is called every time a render occurs,
+which is quite often!
+
+```dart
+class _MyAppState extends State {
+ late Future futureTodo;
+
+ @override
+ void initState() {
+ super.initState();
+ futureTodo = fetchTodo();
+ }
+ // ยทยทยท
+}
+```
-This is the console that comes inside the folder in this case the Flutter console, can use to see which devices connected, create a project in Flutter.
+Finally, to display the data,
+we would want to use the `FutureBuilder` widget.
+As the name implies,
+it's a widget made to handle async data operations.
+
+```dart
+FutureBuilder(
+ future: futureTodo,
+ builder: (context, snapshot) {
+ if (snapshot.hasData) {
+ return Text(snapshot.data!.title);
+ } else if (snapshot.hasError) {
+ return Text('${snapshot.error}');
+ }
+
+ // By default, show a loading spinner.
+ return const CircularProgressIndicator();
+ },
+)
+```
-![Flutter](https://i.imgur.com/rJe2Uao.png)
+The `future` paramter relates to object we want to work with.
+In this case,
+it is a parsed `Todo` object.
-In order to access Flutter commands without having to open this console, we can use the Windows prompt itself we need to add Flutter to the environment variables.
+The `builder` function tells Flutter what needs to be rendered,
+depending on the current state of `Future`,
+which can be *loading*, *success* or *error*.
+Depending on the result of the operation,
+we either show the error,
+the data
+or a loading animation
+while we wait for the http request to fulfill.
-You need to go to the bin folder and copy the path then go to the computer properties, then go to advanced system settings.
+Isn't it easy? ๐
-![Flutter](https://i.imgur.com/tKZP0ZG.png)
+## Local databases
-Click on environment variables, then go to edit path and paste the path to Flutter.
+Sometimes, when writing an app,
+we need to persist and query large amounts of data on the local device.
+In these cases,
+it is beneficial considering using a database
+instead of a local file or a key-value store.
-![Flutter](https://i.imgur.com/OoUtlWO.png) ![Flutter](https://i.imgur.com/1IvNuGT.png)
+In this walkthrough,
+we are going to present two alternatives:
+SQLite and ObjectBox.
+### SQLite
-As you can see if you go to the windows prompt and run the command "flutter" it already appears.
+SQLite is one of the most popular methods for storing data locally.
+For this demo, we will use the package
+[`sqflite`](https://pub.dev/packages/sqflite).
- ![Flutter](https://i.imgur.com/oSCrjRM.png)
-
-
- Now at the windows prompt if you run the command "flutter doctor" will check if there is anything left to install so that we can develop the applications with Flutter.
-
- ![Flutter](https://i.imgur.com/6SMzguK.png)
-
- If you need to install android studio here is the link:https://developer.android.com/studio
- To install dart and flutter just go to the Android studio and press plugins and search for both.
- After having everything installed just open the Android Studio that will appear "Start a new Flutter Project".
-
- ![Flutter](https://i.imgur.com/1zxPJSP.png)
-
-
-
- ## Scaffold class?
-
- Provides a framework which implements the basic material design visual layout structure of the Flutter app.
- Contais various functionality from giving an appbar, a floating button, a drawer, background color, bottom navigation bar and body.
-
-
- ### AppBar
- It defines what has to be displayed at the top of the screen.
- Has various properties like title,padding,brightness.
-
- ### Body
- It's the area below the Appbar and behind the buttons.
- Any widget in the body is positioned at the top left corner by default.
-
-
- ## FloatingActionButton
-
- Is a button displayed floating in the bottom right corner.
- We use this button to promote a primary action in the application.
-
- ## Drawer
-
- Is a panel displayed to the side of the body.
- One usually has to swipe left to right of right to left to access the drawer.
- It uses the Drawer properties which is a material design panel that slides from the edge of a Scaffold to show links in an application.
-
-
-Signing in with Google using Flutter
-==========
-
-After a lot of research I realized that the only way to log in to Google without using Firebase is through the packages provided by Google.
- Using these packages we can make the user log in without saving their personal data, except the data we want to show , like email and username.
-
- ![Flutter](https://i.imgur.com/A4kNbpQ.png)
-
-Let's get started. First of all we have to create a new Flutter Project.
-Choose the Flutter Application option.
-
- ![Flutter](https://i.imgur.com/b6fVARr.png)
-
-Insert the Name of the Project and the company name.
-After that in the 'main.dart' remove all the code except the main and switch the (MyApp) to (MaterialApp).
-Insert 'Title'.
-
- ![Flutter](https://i.imgur.com/dLfAgUO.png)
-
- Create a new class to extend a 'StatefulWidget'.
- Change return 'Container' to 'Scaffold' so we can use the features provided by the Scaffold Class.
-
- ![Flutter](https://i.imgur.com/Dfe11We.png)
-
- Enter in 'pubspec.yaml' , then go to google and search for google sign in package flutter or click this link:
- https://pub.dev/packages/google_sign_in, go to installing and add that command to 'pubspec dependecies'.
-
-
- ![Flutter](https://i.imgur.com/TQ18gDR.png)
-
- ![Flutter](https://i.imgur.com/iUtoy36.png)
-
- Click on "Packages get" as we made a change to the packages to update.
-
-
- ![Flutter](https://i.imgur.com/81h384m.png)
-
- Import the Google package to 'main.dart'.
-
- ![Flutter](https://i.imgur.com/AKibdLx.png)
-
- Create the 'GoogleSignIn' object, provide the scope profile and email to that object.
-
- ![Flutter](https://i.imgur.com/9E5uVXH.png)
-
- You need to create a SignInAccount object.
- Name that object 'user' to be used to see if he is signed in or signed out.
-
- Inside the 'Scaffold' use the 'AppBar' to add a Title.
-
- ![Flutter](https://i.imgur.com/mbgBIlY.png)
-
- In the 'body' create a method '_buildbody'.
- Inside that method you need to add a 'ListTile' to display the information,then use a GoogleUserCircleAvatar to display the profile image.
-
- ![Flutter](https://i.imgur.com/4HObCG5.png)
-
- Make the title and subtitle show the 'Name' and 'Email' on the screen.
- Create a button to be pressed with the text 'Sign out'.
-
-![Flutter](https://i.imgur.com/yUXiw3F.png)
-
-![Flutter](https://i.imgur.com/bLaPMsK.png)
-
-
-
- In short, if the user is not connected, a message appears saying "Not Signed in". If this user is already connected, his data is displayed and a button appears saying "Sign Out".
-
-
-Now, create both _GetSignIn and _GetSignOut methods.
-Use the method silently/ SignInSilently(); -> Used to Sign In the user without interaction.
+Sqflite is one of the most used and updated packages
+to connect to SQLite databases in Flutter.
-![Flutter](https://i.imgur.com/LK7YprE.png)
+#### 1. Add the dependencies
-After the UI is completed we need to register the App in the Firebase.
-Even if we don't need to use Firebase the App has to be registered there.
+To work with SQLite databases,
+we need to import two dependencies.
+We'll use `sqflite` to interact with the SQLite database,
+and `path` to define the location for storing the database on disk.
-So go to: https://console.firebase.google.com/u/0/
-In the Firebase console, add a new project and get the same package name from your app.
+```dart
+dependencies:
+ flutter:
+ sdk: flutter
+ sqflite:
+ path:
+```
-![Flutter](https://i.imgur.com/AGFilMM.png)
+And import the packages in the file you are working in.
-You will need to get the SHA-1 so go to 'gradlew' and to the terminal and run the command- 'gradlew signinReport'.
+```dart
+import 'dart:async';
-![Flutter](https://i.imgur.com/9JD4DGP.png)
+import 'package:flutter/widgets.dart';
+import 'package:path/path.dart';
+import 'package:sqflite/sqflite.dart';
+```
-![Flutter](https://i.imgur.com/YnweBh6.png)
+#### 2. Define a model
-Insert the SHA-1 in the Firebase space then, download the file and paste it inside the Android > app.
+Let's take a look at the data we are going to store.
+Let's define a class for the table
+we are going to create in SQLite.
-Follow the steps to add the Firebase SDK / Go to people API:
+```dart
+class Item {
+ final int id;
+ final String text;
+ final bool completed;
-https://developers.google.com/people/v1/getting-started
+ const Item({
+ required this.id,
+ required this.text,
+ required this.completed,
+ });
-Follow those 3 steps.
+ // Convert an Item into a Map. The keys must correspond to the names of the
+ // columns in the database.
+ Map toMap() {
+ return {
+ 'id': id,
+ 'text': text,
+ 'completed': completed,
+ };
+ }
-![Flutter](https://i.imgur.com/ro6RiCJ.png)
+ // Implement toString to make it easier to see information about
+ // each item when using the print statement.
+ @override
+ String toString() {
+ return 'Item{id: $id, text: $text, completed: $completed}';
+ }
+}
+```
-After that go to credentials and click the user data option.
-Click on the API and go for Android or IOS.
-Name your app again, put the SHA-1 you got from 'gradlew' and the package name you can find in your 'AndroidManifest.xml'.
+#### 3. Open connection to the database
+To open a connection to the SQLite database,
+we are going to define the path
+to the database file using `path`
+**and**
+open the database with `sqflite`.
-![Flutter](https://i.imgur.com/wkLI4L0.png)
+```dart
+WidgetsFlutterBinding.ensureInitialized();
-Set up the consent screen, go to support email and select your email then click save.
-Go to OAuth 2.0 Cliente IDs, select the Android option.
+// Open the database and store the reference.
+final database = openDatabase(
+ // Set the path to the database. Note: Using the `join` function from the
+ // `path` package is best practice to ensure the path is correctly
+ // constructed for each platform.
+ join(await getDatabasesPath(), 'item_database.db'),
+);
+```
-On your Manifest.xml add below the package name this line:
+#### 4. Creating table
+To create the table to store our items,
+we must first verify
+the number of columns and type refer exactly
+to the ones we defined in the class.
+After this,
+it's just a matter of running the appropriate
+`SQL` expression to create the table.
-```ruby
-
+```dart
+final database = openDatabase(
+
+ join(await getDatabasesPath(), 'item_database.db'),
+
+ // When the database is first created, create a table to store items.
+ onCreate: (db, version) {
+ // Run the CREATE TABLE statement on the database.
+ return db.execute(
+ 'CREATE TABLE items(id INTEGER PRIMARY KEY, text TEXT, completed INTEGER)',
+ );
+ },
+ // Set the version. This executes the onCreate function and provides a
+ // path to perform database upgrades and downgrades.
+ version: 1,
+);
```
-Then just edit the button as you like and the Google login is fully functional.
+#### 5. CRUD operations
+
+Now that we have a database created,
+alongside the table,
+to create, update, list and insert Items is quite easy!
+Check the following piece of code.
+
+```dart
+Future crudOperations(Item item) async {
+ // Get a reference to the database
+ final db = await database;
+
+ // Insert an Item into the table.
+ await db.insert('items', item.toMap())
+
+ // Retrieve list of items
+ // and convert the List into a List- .
+ final List
> maps = await db.query('items');
+ Item[] items = List.generate(maps.length, (i) {
+ return Item(
+ id: maps[i]['id'],
+ name: maps[i]['text'],
+ age: maps[i]['completed'],
+ );
+ });
-How to use SQLite in Flutter
-==========
+ // Update the given Item.
+ await db.update(
+ 'items',
+ item.toMap(),
+ // Ensure that the Item has a matching id.
+ where: 'id = ?',
+ // Pass the Item's id as a whereArg to prevent SQL injection.
+ whereArgs: [item.id],
+ );
+
+ // Remove the Item from the database.
+ await db.delete(
+ 'items',
+ // Use a `where` clause to delete a specific item.
+ where: 'id = ?',
+ // Pass the Item's id as a whereArg to prevent SQL injection.
+ whereArgs: [id],
+ );
+}
+```
+And there you have it!
+Here is a quick rundown of the process of creating a database,
+a table
+and applying CRUD operations on it.
+You can leverage this database to hold large amounts of data locally
+(up to a limit, of course)
+instead of relying on common files.
+
+
+### ObjectBox
+
+There are alternatives to SQLite,
+such as Hive and `ObjectBox`.
+In this section,
+we are going to just reference `ObjectBox`
+so the you know there isn't one single
+database option.
+
+`ObjectBox` provides a NoSQL database that uses a
+pure Dart API,
+so there is no need to learn and write SQL expressions.
+There are performance advantages to using this library.
+Make sure to read the
+[package docs](https://github.com/objectbox/objectbox-dart#flutter-database-for-fast-dart-object-persistence-)
+to find out if this option is best for you.
+
+Here is how basic setup
+and CRUD operations would work using `ObjectBox`.
+
+```dart
+// Annotate a Dart class to create a box
+@Entity()
+class Person {
+ @Id()
+ int id;
+ String name;
-![Flutter](https://i.imgur.com/27rAotE.png)
+ Person({this.id = 0, required this.name});
+}
-Persisted data ( persitent Date ) are very important for users, since they would be inconvenient to always be writing your information or wait for the network carry the same data again. In these situations, it would be best to store your data locally.
+// Put a new object into the box
+var person = Person(name: "Joe Green");
+final id = box.put(person);
-# Why SQLite?
+// Get the object back from the box
+person = box.get(id)!;
-SQLite is one of the most popular methods for storing data locally. For this article, we will use the package ( package ) sqflite acceded to SQLite. Sqflite is one of the most used and updated packages to connect to SQLite databases in Flutter.
+// Update the object
+person.name = "Joe Black";
+box.put(person);
+// Query for objects
+final query = (box.query(Person_.name.equal("Joe Black"))
+ ..order(Person_.name)).build();
+final people = query.find();
+query.close();
-# How to use Sqflite on Flutter?
+// Remove the object from the box
+box.remove(person.id);
+```
+## State management
+
+We have previously mentioned state within a widget.
+In stateful widgets,
+the state and how/when it changes
+determines how many times the widget is rendered.
+State that can be neatly contained in a single widget
+is referred as "local state" or **ephemeral state**.
+Other parts of the widget tree
+seldom need to access this kind of state.
+
+However,
+there is state that is *not ephemeral*
+and usually is needed across many widgets of the app.
+This shared state is usually called **application state**.
+Examples of these
+are user preferences or a shopping cart in an e-commerce app.
+
+Consider the following gif,
+taken directly from the `Flutter` docs
+-> https://docs.flutter.dev/development/data-and-backend/state-mgmt/intro
+
+![cart](https://docs.flutter.dev/assets/images/docs/development/data-and-backend/state-mgmt/state-management-explainer.gif)
+
+
+Each widget in the widget tree might have its own local state,
+but there's a piece of *application state*
+(i.e. shared state) in the form of a cart.
+This cart is accessible from any widget of the app -
+in this case, the `MyCart` widget uses it
+to list what item was added to it.
+
+There are [many approaches to state management](https://docs.flutter.dev/development/data-and-backend/state-mgmt/options),
+so it's up to you to decide
+which options are best suited for your use case.
+Many people recommend
+[`Provider`](https://pub.dev/packages/provider) or
+[`Riverpod`](https://riverpod.dev/).
+
+[Bloc](https://bloclibrary.dev/#/)
+is also an increasingly popular alternative
+which forces the logic and the UI
+to be implemented separately.
+
+State management and which alternative is best
+is a [big point of contention](https://www.reddit.com/r/FlutterDev/comments/w4osgi/for_you_what_is_the_best_state_management_with/) between developers.
+There is no bad option,
+just choose whichever you think it's best.
+
+We shall not delve too much into state management
+as shared app state
+is not a beginner-friendly topic to learn
+and is often very opinionated.
+As long as you understood *what it is*,
+that's okay. ๐
+
+### Dependency injection
+
+You might be wondering what dependency injection
+has to do with the aforementioned state management libraries.
+You'll see why this effects *how* the code is structured
+and how it effects testing.
+
+> "[Dependency injection](https://en.wikipedia.org/wiki/Dependency_injection)
+> is a design pattern
+> in which an object or function
+> receives other objects or functions that it depends on."
+
+Let's write an example of dependency injection
+in its simplest form.
+
+```dart
+class LoginService {
+ Api api;
+
+ // Inject the API through the constructor
+ LoginService(this.api)
+}
-## 1. Add the dependency to the project
-In our project, we will open the file pubspec.yaml and search for dependencies. Under dependencies we add the latest version of sqflite and path_provider(which can be removed from pub.dev).
+class Api {}
+```
+Here,
+the `LoginService`
+receives the `Api` object in the constructor,
+something it depends on.
+This is no problem
+if the `LoginService` is one or two levels deep from a widget it uses it.
+However,
+it does become a problem when it's ten levels deep.
+
+```sh
+Widget 1 -> Widget 2 -> Widget 3 -> Widget 4
+```
-```ruby
- dependencies:
- flutter:
+Let's consider we have a `Widget X` that returns a list of albums.
+If `Widget 4` needed these list of albums,
+it would need `Widget X`.
+To do this,
+`Widget X` would need to be passed on
+from `Widget 1` all the way to `Widget 4`
+so `Widget 4` could use it.
+This is not sustainable and it can become nightmarish.
+
+Instead of using a [singleton](https://en.wikipedia.org/wiki/Singleton_pattern)
+which can often lead to unexpected behaviour
+and harder to test codebases,
+we need to use *dependency injection*.
+But in cases of deeply nested widgets,
+using packages like
+[`get_it`](https://pub.dev/packages/get_it) or
+[`Riverpod`](https://riverpod.dev/) or
+[`Provider`](https://pub.dev/packages/provider)
+are the way to go,
+as they give us much better
+control over our dependencies
+*without* any of the drawbacks of creating
+our own singletons with `Singleton.instance`,
+allowing us to inject dependencies
+and accessing values in deeply nested widgets
+without chaining dependencies along the widget tree.
+This is also useful for mocking objects in testing.
+
+If you are interested
+in how you would implement these,
+we highly recommend taking a look
+at this video -> https://www.youtube.com/watch?v=vBT-FhgMaWM&ab_channel=FilledStacks .
+It's a 10 minute video that explains this topic
+in simple terms
+and shows implementation examples
+using `get_it` and `Provider`.
+Great stuff!
+
+
+
+# Testing ๐งช
+
+As in all programming languages, frameworks or platforms,
+the secret to a successful application is to test it _extensively_.
+Implementing tests is not only advantageous to catch bugs
+but also to avoid regression
+when implementing new features.
+
+> To learn more about an example of using TDD:
+> https://github.com/dwyl/flutter-counter-example
+
+> If you are interested in a more thorough introduction
+> to testing and debugging in various IDEs with Flutter, please
+> take a look at the [official docs](https://docs.flutter.dev/testing/debugging)
+
+## Unit testing
+
+Unit testing are handy
+to verify the behaviour of a single function/method/class.
+Let's add some unit tests in Flutter, shall we?
+
+Firstly,
+we ought to import the [`test`](https://pub.dev/packages/test)
+which offers the core functionality for writing tests in Dart.
+
+```dart
+dev_dependencies:
+ test: 1.22.0
+```
+
+And now,
+let's create a simple class
+and a referring test file to test it.
+Create two files so you have the following folder structure.
+
+```
+counter_app/
+ lib/
+ counter.dart
+ test/
+ counter_test.dart
+```
+
+In `counter.dart`,
+add the following piece of code.
+
+```dart
+class Counter {
+ int value = 0;
+
+ void increment() => value++;
+
+ void decrement() => value--;
+}
+```
+
+In `counter_test.dart`,
+add the following:
+
+```dart
+// Import the test package and Counter class
+import 'package:counter_app/counter.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('Counter', () {
+ test('value should start at 0', () {
+ expect(Counter().value, 0);
+ });
+
+ test('value should be incremented', () {
+ final counter = Counter();
+
+ counter.increment();
+
+ expect(counter.value, 1);
+ });
+
+ test('value should be decremented', () {
+ final counter = Counter();
+
+ counter.decrement();
+
+ expect(counter.value, -1);
+ });
+ });
+}
+```
+
+We can group tests using the `group()` function.
+In each `test()`,
+we use the `expect()` function
+to compare expected assertions.
+
+You can type the following command
+to run the tests we just created:
+
+```sh
+flutter test test/counter_test.dart
+```
+
+### Mock testing
+
+Sometimes functions fetch data from web services or databases.
+When we are unit testing these,
+it is inconvenient to do so
+because calling external dependencies may slow down the execution time.
+Needless to say,
+this external dependency
+may sometimes be down,
+amongst other scenarios.
+It's generally a **bad practice**.
+
+In these situations,
+it is useful to **mock**
+these dependencies.
+In Flutter, the *de facto* way
+of mocking classes and objects
+is using the [`mockito`](https://pub.dev/packages/mockito) package.
+
+In this small section,
+we are going to add this dependency,
+create a function to test
+and mock a test file with a mock `http.Client`.
+
+Firstly,
+add the `mockito` package to the `pubspec.yaml` file,
+along with the `flutter_test` dependency
+(will provide core testing functionalities)
+and the `http` package for HTTP requests.
+Do take note that each test dependency
+will be added to the `dev_dependencies` section of the file.
+
+```dart
+dependencies:
+ http: 0.13.5
+dev_dependencies:
+ flutter_test:
sdk: flutter
- sqflite: any
- path_provider: any
+ mockito: 5.3.2
+ build_runner: 2.3.2
+```
+
+Now let's create a function to test.
+This function will fetch data from the internet.
+
+```dart
+Future fetchAlbum(http.Client client) async {
+ final response = await client
+ .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
+
+ if (response.statusCode == 200) {
+ // If the server did return a 200 OK response,
+ // then parse the JSON.
+ return Album.fromJson(jsonDecode(response.body));
+ } else {
+ // If the server did not return a 200 OK response,
+ // then throw an exception.
+ throw Exception('Failed to load album');
+ }
+}
```
+You might have noticed
+the `http.Client` is provided to the argument.
+This makes it so that the client
+that fetches data changes according to any situation.
+In Flutter, we can provide an `http.IOClient`.
+For testing, we can pass a mock `http.Client`.
+
+In a test file,
+we will add an an annotation
+to the main function to generate a `MockClient` class with `mockito`.
+According to the argument passed to the annotation,
+the generated `MockClient` class will implement it.
+When generating,
+the mocks will be located in a file named `XX_test.mocks.dart`.
+We will import this file to use them.
+For now, create a test file where we will add tests.
+
+```dart
+import 'package:http/http.dart' as http;
+import 'package:mocking/main.dart';
+import 'package:mockito/annotations.dart';
+
+// Generate a MockClient using the Mockito package.
+// Create new instances of this class in each test.
+@GenerateMocks([http.Client])
+void main() {
+}
+```
-## 2. Creating a DB Client
-Now, in our project, we will create a new Database.dart file.
+Now run `flutter pub run build_runner build`.
+This command will generate the mocks in `XX_test.mocks.dart`.
+Now we can use these mocks in our tests!
+Let's add two:
+one for a successful request
+and another for a failing one,
+and catch the raised exception.
+
+```dart
+import 'package:flutter_test/flutter_test.dart';
+import 'package:http/http.dart' as http;
+import 'package:mocking/main.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+import 'fetch_album_test.mocks.dart';
+
+// Generate a MockClient using the Mockito package.
+// Create new instances of this class in each test.
+@GenerateMocks([http.Client])
+void main() {
+ group('fetchAlbum', () {
+ test('returns an Album if the http call completes successfully', () async {
+ final client = MockClient();
+
+ // Use Mockito to return a successful response when it calls the
+ // provided http.Client.
+ when(client
+ .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')))
+ .thenAnswer((_) async =>
+ http.Response('{"userId": 1, "id": 2, "title": "mock"}', 200));
+
+ expect(await fetchAlbum(client), isA());
+ });
-1- Creation of a private builder that can be used only within the class:
+ test('throws an exception if the http call completes with an error', () {
+ final client = MockClient();
+ // Use Mockito to return an unsuccessful response when it calls the
+ // provided http.Client.
+ when(client
+ .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')))
+ .thenAnswer((_) async => http.Response('Not Found', 404));
-```ruby
-class DBProvider {
- DBProvider._();
- static final DBProvider db = DBProvider._();
+ expect(fetchAlbum(client), throwsException);
+ });
+ });
}
```
-2- Preparation of the database
-Next we will create the database object and provide you with a getter, which will create an instance of the database, if it has not already been created.
+In these tests,
+we are **importing** the generated mocks
+(`fetch_album_test.mocks.dart`).
+Plus, we create the `MockClient()`,
+define the behaviour we expect the mock to do,
+and then pass it to the function,
+effectively asserting its output.
+
+We can run the tests
+and see if they fail or not by running
+
+```sh
+flutter test test/fetch_album_test.dart
+```
+
+Congratulations!
+You just mocked a `http.Client` object
+and properly tested a function
+that used an external dependency.
+`mockito` has many other features.
+You can read about them
+[in their documentation](https://pub.dev/packages/mockito).
+
+## Integration testing
+
+While unit testing is useful
+for testing individual classes, functions or widgets,
+they don't test how all of these *work together*, as a whole.
+These tasks are captured
+and tested with **integration tests**.
+
+> There is a concept in Flutter that is **widget testing**.
+> Widget testing tests a single widget
+> while integration testing can test a complete app or large parts of it.
+> Integration testing will require *a device* or *emulator*.
+> So, it should be used sparingly
+> and to capture behaviours
+> that were missed by unit testing and widget testing.
+>
+> Implementation-wise,
+> widget testing uses the `testWidget` function,
+> much like integration tests.
+> So they *can be similar*.
+
+We can luckily leverage the SDK's
+[`integration_test`](https://github.com/flutter/flutter/tree/main/packages/integration_test)
+package to do this.
+
+Let's start by creating a super simple app.
+This app will just have a button
+and a counter displaying the number of time the button was clicked.
+
+But, before that,
+let's add the needed dependencies.
+We'll be adding the `integration_test`
+and `flutter_test` packages
+to the `dev_dependencies` section of `pubspec.yaml`.
+
+```yaml
+dev_dependencies:
+ integration_test:
+ sdk: flutter
+ flutter_test:
+ sdk: flutter
+```
+
+Now, let's create our app.
+In `lib/app.dart`,
+let's use the following piece of code.
+```dart
+import 'package:flutter/material.dart';
-```ruby
-static Database _database;
+void main() => runApp(const MyApp());
- Future get database async {
- if (_database != null)
- return _database;
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
- // if _database is null we instantiate it
- _database = await initDB();
- return _database;
+ @override
+ Widget build(BuildContext context) {
+ return const MaterialApp(
+ title: 'Counter App',
+ home: MyHomePage(title: 'Counter App Home Page'),
+ );
}
+}
+
+class MyHomePage extends StatefulWidget {
+ const MyHomePage({super.key, required this.title});
+
+ final String title;
+
+ @override
+ State createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State {
+ int _counter = 0;
+
+ void _incrementCounter() {
+ setState(() {
+ _counter++;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(widget.title),
+ ),
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const Text(
+ 'You have pushed the button this many times:',
+ ),
+ Text(
+ '$_counter',
+ // Provide a Key to this specific Text widget. This allows
+ // identifying the widget from inside the test suite,
+ // and reading the text.
+ key: const Key('counter'),
+ style: Theme.of(context).textTheme.headline4,
+ ),
+ ],
+ ),
+ ),
+ floatingActionButton: FloatingActionButton(
+ // Provide a Key to this button. This allows finding this
+ // specific button inside the test suite, and tapping it.
+ key: const Key('increment'),
+ onPressed: _incrementCounter,
+ tooltip: 'Increment',
+ child: const Icon(Icons.add),
+ ),
+ );
+ }
+}
```
-If no objects are assigned to the database, we use the initDB function to create the database. In this function, we get the directory where we will store the database and create the tables we want:
+Now, inside `integration_test/app_test.dart`,
+we are going to test the action of clicking
+and checking if the counter is incremented.
+For this, we will initialize a singleton service `IntegrationTestWidgetsFlutterBinding`,
+which executes the tests on a physical device,
+and leverage the `WidgetTester` class
+to interact with the widgets.
+
+```dart
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import 'package:counter_app/main.dart' as app;
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+ group('end-to-end test', () {
+ testWidgets('tap on the floating action button, verify counter',
+ (tester) async {
+ app.main();
+ await tester.pumpAndSettle();
-```ruby
-initDB() async {
- Directory documentsDirectory = await getApplicationDocumentsDirectory();
- String path = join(documentsDirectory.path, "TestDB.db");
- return await openDatabase(path, version: 1, onOpen: (db) {
- }, onCreate: (Database db, int version) async {
- await db.execute("CREATE TABLE Client ("
- "id INTEGER PRIMARY KEY,"
- "first_name TEXT,"
- "last_name TEXT,"
- "blocked BIT"
- ")");
+ // Verify the counter starts at 0.
+ expect(find.text('0'), findsOneWidget);
+
+ // Finds the floating action button to tap on.
+ final Finder fab = find.byTooltip('Increment');
+
+ // Emulate a tap on the floating action button.
+ await tester.tap(fab);
+
+ // Trigger a frame.
+ await tester.pumpAndSettle();
+
+ // Verify the counter increments by 1.
+ expect(find.text('1'), findsOneWidget);
});
+ });
+}
+```
+
+Running these on mobile devices is the same
+process as before -
+just run `flutter test integration_test`.
+However,
+if you were to run these on a web browser,
+you'd need to download [`ChromeDriver`](https://chromedriver.chromium.org/downloads),
+create a file in `test_driver/integration_test.dart` with
+
+```dart
+import 'package:integration_test/integration_test_driver.dart';
+
+Future main() => integrationDriver();
+```
+
+and open two terminal windows.
+In the first one, we launch `chromedriver` with
+
+```sh
+chromedriver --port=4444
+```
+
+and in the other,
+from the root of the project,
+run `flutter`
+with the drive file path we just created
+and targeting the test file we want to test,
+like so:
+
+```dart
+flutter drive \
+ --driver=test_driver/integration_test.dart \
+ --target=integration_test/app_test.dart \
+ -d chrome
+```
+
+And you're done!
+Congratulations, you just unit
+*and* integration tested your application.
+Awesome work! ๐
+
+# App demo ๐ฑ
+
+We've learnt a lot about basic Flutter principles.
+There is no better way of learning them
+by creating an app and applying them!
+In this section,
+we'll walk you through
+to creating an application that fetches information from a rest API,
+lists them
+and allows the user to choose his favourites.
+
+Let's get cracking!
+
+## 0. Setting up a new project
+
+In this walkthrough,
+we are going to use Visual Studio Code.
+We will assume you have this IDE installed,
+as well as the `Flutter` and `Dart` extensions installed.
+If not, do so.
+
+
+
+After restarting Visual Studio Code,
+let's create a new project!
+Click on `View > Command Palette`,
+type `Flutter`
+and click on
+`Flutter: New Project`.
+It will ask you for a name of the new project -
+just type something like "demo_app" -
+and then click `Enter`
+and your project should start setting up!
+
+Let's run our newly created app.
+On the bottom menu of Visual Studio Code,
+click on the device button
+and you are shown a menu
+asking you to choose a device you want to run the app from.
+I'll be going with iPhone 14 Pro Max.
+
+Bottom menu | After clicking, you are prompted with this menu
+:-------------------------:|:-------------------------:
+![](https://user-images.githubusercontent.com/17494745/200813538-ceb06084-95ed-492f-940e-27ceaf86c6da.png) | ![](https://user-images.githubusercontent.com/17494745/200813745-5c75d190-5306-4f7c-88da-cffea66d4a27.png)
+
+After setting up the device,
+the emulator should be shown.
+After that,
+in Visual Studio Code,
+click on `Run > Start debugging`.
+The build process will start and,
+after it is finished,
+the app will start on the newly created emulator.
+You should now see an "Hello World" app running.
+Awesome! ๐
+
+
+
+## 1. Project structure
+
+We could implement a really simple project structure for this demo.
+But, just for learning purposes,
+let's implement a structure
+that is divided into four layers:
+
+- [**presentation**](https://codewithandrea.com/articles/flutter-presentation-layer/):
+consisting of widgets, states (either local or shared) and controllers.
+- [**application**](https://codewithandrea.com/articles/flutter-app-architecture-application-layer/):
+in this layer we will have *services*, which will fetch data from the *data layer*.
+- [**domain layer**](https://codewithandrea.com/articles/flutter-app-architecture-domain-model/):
+where we define domain classes for the business logic.
+- [**data**](https://codewithandrea.com/articles/flutter-repository-pattern/):
+our data sources and repositories. We will interact with APIs here.
+
+
+![structure](https://codewithandrea.com/articles/flutter-project-structure/images/flutter-app-architecture.webp)
+
+> This structure borrows many concepts from
+[DDD (Domain-driven-design)](https://en.wikipedia.org/wiki/Domain-driven_design),
+where the codebase is modeled
+and implemented according to domain logic and concepts.
+
+We will simplify these four layers
+because it is a small project.
+But if you were to work in a corporate environmnent,
+you would be dealing
+with various APIs, data sources and a large amount of models.
+This structure makes it much easier to maintain code at a larger scale.
+Although we might be breaking \
+the [YAGNI](https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it) principle here,
+this is just to show how to structure your code
+in a maintainable manner.
+
+Let's start!
+Firstly create the following folder structure.
+
+```
+lib
+ - models
+ todo.dart
+ - repository
+ todoRepository.dart
+ - services
+ todoService.dart
+ main.dart
+```
+
+In the `lib/models/todo.dart` file,
+add the following piece of code.
+
+```dart
+class Todo {
+ final int userId;
+ final int id;
+ final String title;
+ final bool completed;
+
+ const Todo({
+ required this.userId,
+ required this.id,
+ required this.title,
+ required this.completed,
+ });
+
+ factory Todo.fromJson(Map json) {
+ return Todo(
+ userId: json['userId'],
+ id: json['id'],
+ title: json['title'],
+ completed: json['completed'],
+ );
}
+}
```
-## 3. Creation of Model Class
-The data inside our database will be converted to Dart Maps, so first we need to create the Model Classes with the 'toMap' and 'fromMap' methods.
+This should be nothing new to you.
+We declared each member field
+and added a function
+that parses a JSON object to the class.
+Next up,
+let's head to the repository file.
+Firstly, run `flutter pub add http`
+to install the `http` package,
+as we are going to need it to fetch data from a third-party API.
-```ruby
+After that,
+let's create the `lib/repository/todoRepository.dart` file.
-/// ClientModel.dart
+```dart
import 'dart:convert';
-Client clientFromJson(String str) {
- final jsonData = json.decode(str);
- return Client.fromMap(jsonData);
+import 'package:demo_app/models/todo.dart';
+import 'package:http/http.dart' as http;
+
+abstract class TodoRepository {
+ Future> getTodos();
}
-String clientToJson(Client data) {
- final dyn = data.toMap();
- return json.encode(dyn);
+class HTTPTodoRepository implements TodoRepository {
+ @override
+ Future> getTodos() async {
+ final response =
+ await http.get(Uri.parse("https://jsonplaceholder.typicode.com/todos"));
+
+ if (response.statusCode == 200) {
+ Iterable l = json.decode(response.body);
+ List todos =
+ List.from(l.map((model) => Todo.fromJson(model)));
+
+ return todos;
+ } else {
+ throw Exception('Failed to load Todo\'s.');
+ }
+ }
}
+```
-class Client {
- int id;
- String firstName;
- String lastName;
- bool blocked;
-
- Client({
- this.id,
- this.firstName,
- this.lastName,
- this.blocked,
- });
+Here, we are creating an `abstract` class,
+which will serve as an interface for creating the `HTTPTodoRepository` class.
+The class,
+since implements the `TodoRepository` abstract class,
+will have to implement the `getTodos()` function.
+In this function,
+we will call an API which returns an array of todos.
+In case the call is successful,
+we parse each decoded json object
+and convert it to a `Todo` object.
+
+Now let's go and implement the `lib/services/todoService.dart` file.
- factory Client.fromMap(Map json) => new Client(
- id: json["id"],
- firstName: json["first_name"],
- lastName: json["last_name"],
- blocked: json["blocked"] == 1,
- );
+```dart
+import 'package:demo_app/models/todo.dart';
+import 'package:demo_app/repository/todoRepository.dart';
- Map toMap() => {
- "id": id,
- "first_name": firstName,
- "last_name": lastName,
- "blocked": blocked,
- };
+class TodoService {
+ late final TodoRepository todoRepository;
+
+ TodoService() {
+ todoRepository = HTTPTodoRepository();
+ }
+
+ Future> getTodos() => todoRepository.getTodos();
}
```
+In this class,
+we initialize it by creating a `TodoRepository`.
+We use this field member in the `getTodos()` function,
+which in turn,
+calls the `TodoRepository's` function to fetch the todos list.
+
+You might be asking yourself:
+"Well, mate, that's a lot of work
+for just a simple fetching function, isn't it?".
+Well, in this case, you'd be right.
+But we're just learning a maintainable way of structuring our code.
+This service might
+(and *is*, in this case)
+redudant.
+But imagine if we have widgets
+that necessitate objects
+that stem from various data sources.
+It will be *the service's job*
+to fetch whatever data is needed from each repository,
+compile it
+and give it to the widget.
+
+Let's continue.
+In the `main.dart` file,
+let's fetch the todo list
+and show the first one,
+just to check that everything works.
+Import the service and the models.
+
+```dart
+import 'package:demo_app/models/todo.dart';
+import 'services/todoService.dart';
+```
+and then change the `_MyHomePageState` class, like so.
-## 4. CRUD Operations
+```dart
+class _MyHomePageState extends State {
+ late Future> futureTodosList;
-Using 'Insert':
+ @override
+ void initState() {
+ super.initState();
+ futureTodosList = TodoService().getTodos();
+ }
+ @override
+ Widget build(BuildContext context) {
+ // This method is rerun every time setState is called, for instance as done
+ // by the _incrementCounter method above.
+ //
+ // The Flutter framework has been optimized to make rerunning build methods
+ // fast, so that you can just rebuild anything that needs updating rather
+ // than having to individually change instances of widgets.
+ return Scaffold(
+ appBar: AppBar(
+ // Here we take the value from the MyHomePage object that was created by
+ // the App.build method, and use it to set our appbar title.
+ title: Text(widget.title),
+ ),
+ body: Center(
+ // Center is a layout widget. It takes a single child and positions it
+ // in the middle of the parent.
+ child: FutureBuilder>(
+ future: futureTodosList,
+ builder: (context, snapshot) {
+ if (snapshot.hasData) {
+ return Text(snapshot.data![0].title);
+ } else if (snapshot.hasError) {
+ return Text('${snapshot.error}');
+ }
+
+ // By default, show a loading spinner.
+ return const CircularProgressIndicator();
+ },
+ ),
+ ),
+ )
+ ;
+ }
+}
+```
-```ruby
+If you re-run the app, you should see something like this.
+It is displaying the first todo title from the fetched list.
+We used the `FutureBuilder` class
+to indicate that the data residing within will come at a later stage -
+the todo list.
+If the data comes,
+we show the first todo title.
+And we did all this in a `StatefulWidget`,
+with the `State`.
+
+In the `_MyHomePageState` class,
+we declared that a `Future` todos list is expected
+and is later fetched in the `initState()` method -
+it only runs one time, which is exactly what we want.
+
+Hurray, we just set up all the data we need!
+Now it's just about making
+our app look pretty โจ.
+
+
+
+## 2. Creating a list of todos
+
+Let's create a new widget to encapsulate our todo list.
+In Visual Studio Code, at the end of the `main.dart` file,
+click `Enter` a few times and type `stful`.
+The IDE will ask if we want to create a Stateful or Stateless widget.
+Since we already get the information on the `HomePage` `Stateful Widget`,
+we will pass it down to a `Stateless Widget` we are going to now create.
+
+Name your new `Stateless Widget` "`TodoList`".
+Check the following code and use it.
+
+```dart
+class TodoList extends StatelessWidget {
+ const TodoList({required this.todoList, super.key});
+
+ final List todoList;
+
+ @override
+ Widget build(BuildContext context) {
+ return ListView.builder(
+ itemCount: todoList.length,
+ padding: const EdgeInsets.all(16.0),
+ itemBuilder: (context, i) {
+ if (i.isOdd) return const Divider();
+ final index = i ~/ 2;
+
+ return ListTile(
+ title: Text(
+ todoList[index].title,
+ style: const TextStyle(fontSize: 18),
+ ),
+ );
+ },
+ );
+ }
+}
+```
-newClient(Client newClient) async {
- final db = await database;
- var res = await db.insert("Client", newClient.toMap());
- return res;
+This new stateless widget receives a `todoList` as argument.
+This widget will return a `ListView` widget,
+which has a `itemBuilder` property
+that will render a list of items.
+
+In the `itemCount` property,
+we will tell how many items we want the list to show.
+In this case, we want the length of the todo list.
+
+In the `padding` property,
+we will add an
+[`EdgeInsets.all()`](https://api.flutter.dev/flutter/painting/EdgeInsets-class.html)
+spacing widget.
+This will add a spacing of `16.0` on all directions (up, right, left, down).
+
+In the `itemBuilder` property,
+we get access to the `context` and `index` of the rendered component.
+We are adding a `Divider` in between every item.
+So, the `i` value *includes* the `Divider` components as well.
+Therefore, to correctly fetch the index of the item in the list,
+we will use the ListView index
+and use the [`~/`](https://api.flutter.dev/flutter/dart-core/double/operator_truncate_divide.html) operator.
+This will yield integer part of a division.
+For example, `1 2 3 4 5` will be `0 1 1 2 2`.
+
+Now, let's use this new widget and change the `_MyHomePageState`,
+more specifically the `FutureBuilder.builder` return value.
+
+```dart
+ if (snapshot.hasData) {
+ return TodoList(todoList: snapshot.data!);
+ } else if (snapshot.hasError) {
+ return Text('${snapshot.error}');
}
```
-Get Client via an ID:
+You should now be able to scroll the list, like so!
+
+
+## 3. Adding interactivity
-```ruby
+We want to be able to click on a todo item
+and mark it as "completed".
+To do this, we ought to add interactivity to our `TodoList`.
+To do this, we got to convert our stateless widget
+into a *stateful widget*.
+Doing this is fairly simple with Visual Studio Code.
+Simply double-click on `TodoList`
+and a yellow lightbulb will appear to the left side.
+Simply click it
+and click in `Convert to Stateful Widget`.
-getClient(int id) async {
- final db = await database;
- var res =await db.query("Client", where: "id = ?", whereArgs: [id]);
- return res.isNotEmpty ? Client.fromMap(res.first) : Null ;
+
+
+This will effectively create a new `State`
+to the widget and add to it.
+You should now have the following code:
+
+```dart
+class TodoList extends StatefulWidget {
+ const TodoList({required this.todoList, super.key});
+
+ final List todoList;
+
+ @override
+ State createState() => _TodoListState();
+}
+
+class _TodoListState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return ListView.builder(
+ itemCount: widget.todoList.length,
+ padding: const EdgeInsets.all(16.0),
+ itemBuilder: (context, i) {
+ if (i.isOdd) return const Divider();
+ final index = i ~/ 2;
+
+ return ListTile(
+ title: Text(
+ widget.todoList[index].title,
+ style: const TextStyle(fontSize: 18),
+ ),
+ );
+ },
+ );
}
+}
```
-Obtain all Clients with one condition:
-In this example, we use rawQuery to map the results list to a list of Client objects:
+You now have the `TodoList`
+and `_TodoListState`, which refers to the state of the former.
+Notice it is preceded with an underscore.
+This enforces privacy
+and is best practice for `State` objects and private fields.
-```ruby
+Let's change the widget to look like the following:
-getAllClients() async {
- final db = await database;
- var res = await db.query("Client");
- List list =
- res.isNotEmpty ? res.map((c) => Client.fromMap(c)).toList() : [];
- return list;
+```dart
+
+class TodoList extends StatefulWidget {
+ const TodoList({required this.todoList, super.key});
+
+ final List todoList;
+
+ @override
+ State createState() => _TodoListState();
+}
+
+class _TodoListState extends State {
+ final Set _doneList = {};
+
+ @override
+ Widget build(BuildContext context) {
+ return ListView.builder(
+ itemCount: widget.todoList.length,
+ padding: const EdgeInsets.all(16.0),
+ itemBuilder: (context, i) {
+
+ final index = i ~/ 2;
+ final todoObj = widget.todoList[index];
+
+ if (i.isOdd) return const Divider();
+
+ final completed = todoObj.completed || _doneList.contains(todoObj);
+
+ return ListTile(
+ title: Text(
+ todoObj.title,
+ style: TextStyle(
+ fontSize: 18,
+ decoration: completed
+ ? TextDecoration.lineThrough
+ : TextDecoration.none),
+ ),
+ onTap: (() {
+ setState(() {
+ if (completed) {
+ _doneList.remove(todoObj);
+ } else {
+ _doneList.add(todoObj);
+ }
+ });
+ }),
+ );
+ },
+ );
+ }
+}
+```
+
+Let's break it down.
+The `State` object (`_TodoListState`) now has a `_doneList` set.
+This set (a set is like a list but guarantees each object is unique),
+as the underscore symbol entails,
+is private.
+This list will hold *the list of todos marked as **done***.
+
+Inside the `ListView.builder()` widget,
+we have changed the `itemBuilder`.
+We have added the following line:
+
+```dart
+final completed = _doneList.contains(todoObj);
+```
+
+We are checking the item is in the `_doneList` set.
+If so,
+we will add a strikethrough effect on the text to symbolize this.
+
+```dart
+ title: Text(
+ todoObj.title,
+ style: TextStyle(
+ fontSize: 18,
+ decoration: completed
+ ? TextDecoration.lineThrough
+ : TextDecoration.none),
+ ),
+```
+
+Now, the only thing that is left to do
+is to mark a todo item as *complete* or *incomplete* by tapping it.
+Inside the `ListTile`,
+let's add an `onTap` property,
+which is called everytime the list item is tapped,
+and change the state accordingly.
+If the item is completed,
+we mark it as incomplete, and vice-versa.
+
+```dart
+ onTap: (() {
+ setState(() {
+ if (completed) {
+ _doneList.remove(todoObj);
+ } else {
+ _doneList.add(todoObj);
+ }
+ });
+ }),
+```
+
+Now, if you open your app,
+you can scroll and check items
+and set them as `done` and reverse that action.
+Great job!
+
+![interactivity](https://user-images.githubusercontent.com/17494745/200861445-b4550a49-98cc-4f80-ba02-6ceff7fa17da.gif)
+
+## 4. Adding navigation
+
+We have added a stateful widget
+and are keeping track of what todos are marked as `completed` or not.
+It would be great to actually have a page
+where we see this list of completed items.
+
+Currently, our widget tree looks like this.
+`MyApp`
+-> `MyHomePage` (which has the `todoList` as local state)
+-> `TodoList` (which has the `doneList` as local state).
+
+We need to merge `MyHomePage` and `TodoList`
+into a single widget
+that has the `todoList` and `doneList` fields
+so we are be able to add navigation.
+Merging these two in one
+will lead to a new `TodoList` widget,
+that will look like this.
+
+```dart
+class TodoList extends StatefulWidget {
+ const TodoList({super.key});
+
+ @override
+ State createState() => _TodoListState();
+}
+
+class _TodoListState extends State {
+ late Future> futureTodosList;
+ final Set _doneList = {};
+
+ @override
+ void initState() {
+ super.initState();
+ futureTodosList = TodoService().getTodos();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('todo item list'),
+ ),
+ body: FutureBuilder>(
+ future: futureTodosList,
+ builder: (context, snapshot) {
+ if (snapshot.hasData) {
+ final todolist = snapshot.data!;
+
+ return ListView.builder(
+ itemCount: todolist.length,
+ padding: const EdgeInsets.all(16.0),
+ itemBuilder: (context, i) {
+ final index = i ~/ 2;
+ final todoObj = todolist[index];
+
+ if (i.isOdd) return const Divider();
+
+ final completed = _doneList.contains(todoObj);
+
+ return ListTile(
+ title: Text(
+ todoObj.title,
+ style: TextStyle(
+ fontSize: 18,
+ decoration: completed
+ ? TextDecoration.lineThrough
+ : TextDecoration.none),
+ ),
+ onTap: (() {
+ setState(() {
+ if (completed) {
+ _doneList.remove(todoObj);
+ } else {
+ _doneList.add(todoObj);
+ }
+ });
+ }),
+ );
+ },
+ );
+ } else if (snapshot.hasError) {
+ return Text('${snapshot.error}');
+ }
+
+ // By default, show a loading spinner.
+ return const CircularProgressIndicator();
+ },
+ ));
}
+}
+
+```
+
+Nothing was fundamentally changed.
+We wrapped the `TodoList`
+with the same widgets of the `MyHomePage` widget.
+We also changed the `AppBar.title`
+to `Text('todo item list')`.
+
+We now also need to change the `MyApp`
+to call this newly edited widget.
+
+```dart
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const MaterialApp( // MODIFY with const
+ title: 'FlutterDemo',
+ home: TodoList(), // REMOVE Scaffold
+ );
+ }
+}
```
-Update an existing Client:
+If you run the application,
+it looks the same as before.
+The only difference now is that we have all the state in the same widget
+(`TodoList`).
+
+Inside the `_TodoListState` widget state class,
+let's add a button in the app bar
+to navigate to the new page.
+
+```dart
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('todo item list'),
+ actions: [
+ IconButton(
+ icon: const Icon(Icons.list),
+ onPressed: _pushCompleted,
+ tooltip: 'completed todo list',
+ ),
+ ],
+ ),
+```
-```ruby
+Let's implement the `_pushCompleted` function,
+that is executed everytime the icon button is clicked on the appbar.
+We want to navigate to the page
+that shows the completed todo items.
+Add the following function in `_TodoListState`.
+
+```dart
+ void _pushCompleted() {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (context) {
+ final tiles = _doneList.map(
+ (todo) {
+ return ListTile(
+ title: Text(
+ todo.title,
+ style: const TextStyle(fontSize: 18),
+ ),
+ );
+ },
+ );
+ final divided = tiles.isNotEmpty
+ ? ListTile.divideTiles(
+ context: context,
+ tiles: tiles,
+ ).toList()
+ : [];
+
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('completed todo list'),
+ ),
+ body: ListView(children: divided),
+ );
+ },
+ ),
+ );
+ }
+```
-updateClient(Client newClient) async {
- final db = await database;
- var res = await db.update("Client", newClient.toMap(),
- where: "id = ?", whereArgs: [newClient.id]);
- return res;
+Let's break this down.
+We use the `Navigator` to push a new screen to the stack.
+We pass the widget's `context`
+and then use the `push()` function to add the screen to the stack.
+In this case,
+we are pushing a `MaterialPageRoute`.
+Inside the `builder` property,
+we return a `Scaffold` object with an `appBar` and a `body`.
+Inside this `body`,
+we are rendering a `ListView` with each todo item inside the `_doneList` set.
+
+Since we are using `MaterialPageRoute` and `Scaffold`,
+the back button is automatically added to the appbar,
+making it possible to *pop* the screen
+and go back to the screen showing the todo list.
+
+If we rerun our app,
+we can now navigate between pages.
+Hurray! ๐
+
+![navigation](https://user-images.githubusercontent.com/17494745/200880357-314bb388-5c0c-4955-ac22-f9ec59e418a6.gif)
+
+## 5. Finishing touches
+
+We can quickly customize the theme of the app and its title.
+Let's change the colors and give our fancy app a new one.
+
+```dart
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Todo App',
+ theme: ThemeData(
+ appBarTheme: const AppBarTheme(
+ backgroundColor: Colors.black,
+ foregroundColor: Colors.white,
+ )
+ ),
+ home: const TodoList(),
+ );
}
```
-Delete a Client:
+Your app should look like this now!
+You can choose the colors you like.
+Go creative!
+
+
+
+## 6. Testing!
+
+We have our app running.
+In fact, we should have used a [TDD](https://github.com/dwyl/learn-tdd)
+approach whilst developing our app.
+The reason we didn't do this
+is to show you how some code needs to be laid out
+to be *testable*.
+
+As you have previously seen,
+mocking objects in Flutter
+works through **dependency injection**.
+That is, these are functions receive the dependencies
+that they depend on through, for example, their constructor.
+
+For simplicity sake,
+we are not going to be using any libraries like `get_it` or `Riverpod`
+to do deeply nested dependency injection.
+In our demo app,
+we only have two levels deep,
+so mocking and testing is very simple.
+
+Let's start testing!
+
+### 6.1 Unit testing
+
+Let's start unit testing our `TodoRepository` and `TodoService`.
+As it stands, both of these files are not "testable".
+We ought to find a way to mock the `http` requests.
+How do we do that?
+Exactly.
+*Dependency injection*.
+
+But first,
+we need to add the dependencies in `pubspec.yaml`.
+In the `dev_dependencies` section,
+add the following two lines of code.
+
+```yaml
+ mockito: 5.3.2
+ build_runner: 2.3.2
+```
+
+And run `flutter pub get`.
+This will download the newly added dependencies.
+Now let's start testing!
+
+Change `lib/repository/todoRepository.dart`
+so it looks like the following.
+
+```dart
+import 'package:http/http.dart' show Client;
-```ruby
+class HTTPTodoRepository implements TodoRepository {
+ Client client = Client();
+ @override
+ Future> getTodos() async {
+ final response = await client
+ .get(Uri.parse("https://jsonplaceholder.typicode.com/todos"));
-deleteClient(int id) async {
- final db = await database;
- db.delete("Client", where: "id = ?", whereArgs: [id]);
+ if (response.statusCode == 200) {
+ Iterable l = json.decode(response.body);
+ List todos =
+ List.from(l.map((model) => Todo.fromJson(model)));
+
+ return todos;
+ } else {
+ throw Exception('Failed to load Todo\'s.');
+ }
}
+}
+```
+
+Now, on to testing.
+Create a directory in `test/unit`
+and add a new file `todoRepository_test.dart`.
+
+```dart
+import 'package:http/http.dart' as http;
+import 'package:mockito/annotations.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+@GenerateMocks([http.Client])
+void main() {
+}
+```
+
+We are going to use `mockito`'s `@GenerateMocks` annotation
+to generate a mock object for the `http.Client`,
+which is used inside the function.
+We could do it manually
+but since we can get it generated to ourselves automatically,
+we should take advantage of this.
+
+Run the following command.
+
+```sh
+flutter pub run build_runner build --delete-conflicting-outputs
+```
+
+This will generate
+a `todoRepository_test.mocks.dart` file with the generated mocks.
+Import the file in the `todoRepository_test.dart` file
+and let's create our first tests!
+
+```dart
+import 'todoRepository_test.mocks.dart';
+
+@GenerateMocks([http.Client])
+void main() {
+ test('Checks if a Todo array is yielded and has the expected length',
+ () async {
+ final client = MockClient();
+ final repo = HTTPTodoRepository();
+
+ // Use Mockito to return a successful response when it calls the
+ // provided http.Client.
+ when(client.get(Uri.parse('https://jsonplaceholder.typicode.com/todos')))
+ .thenAnswer((_) async => http.Response(
+ '[{"userId": 1, "id": 2, "title": "mock", "completed": true}]',
+ 200));
+
+ repo.client = client;
+
+ expect(await repo.getTodos().then((value) => value.length), equals(1));
+ });
+
+ test('throws an exception if the http call completes with an error', () {
+ final client = MockClient();
+ final repo = HTTPTodoRepository();
+
+ // Use Mockito to return an unsuccessful response when it calls the
+ // provided http.Client.
+ when(client.get(Uri.parse('https://jsonplaceholder.typicode.com/todos')))
+ .thenAnswer((_) async => http.Response('Not Found', 404));
+
+ repo.client = client;
+
+ expect(repo.getTodos(), throwsException);
+ });
+}
```
-Delete All Clients:
+Let's break down how we are testing the repository.
+We are creating a `final client = MockClient()`
+using the generated `MockClient`
+from the `todoRepository_test.mocks.dart` file.
+We are specifying that this client
+will return an array with a single todo item.
+Using this new `MockClient`,
+we replace the class `client` with the `MockClient`
+and run the test.
+The same procedure is done,
+except an exception is expected to rise.
+
+Let's do the same process for the `TodoService.dart` file.
+We need to change it, like so.
+
+```dart
+import 'package:demo_app/models/todo.dart';
+import 'package:demo_app/repository/todoRepository.dart';
+
+class TodoService {
+ TodoRepository todoRepository = HTTPTodoRepository();
+
+ Future> getTodos() => todoRepository.getTodos();
+}
+```
+
+
+Create a new `todoService_test.dart`
+and add the following lines of code.
-```ruby
+```dart
+import 'package:http/http.dart' as http;
+import 'package:mockito/annotations.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+@GenerateMocks([http.Client])
+void main() {
+}
+```
+
+Run the following command.
+
+```sh
+flutter pub run build_runner build --delete-conflicting-outputs
+```
+
+This will generate a
+`todoService_test.mocks.dart` file with the generated mocks.
+Similarly, we will use this file for the tests in the same fashion as before.
+In `todoService_test.dart`, add the following code.
+
+```dart
+import 'todoService_test.mocks.dart';
+
+// Generate a MockClient using the Mockito package.
+// Create new instances of this class in each test.
+@GenerateMocks([http.Client])
+void main() {
+ test(
+ 'Checks if a Todo array is returned from the service and has the expected length',
+ () async {
+ final client = MockClient();
+ final repo = HTTPTodoRepository();
+
+ // Use Mockito to return a successful response when it calls the
+ // provided http.Client.
+ when(client.get(Uri.parse('https://jsonplaceholder.typicode.com/todos')))
+ .thenAnswer((_) async => http.Response(
+ '[{"userId": 1, "id": 2, "title": "mock", "completed": true}]',
+ 200));
+
+ repo.client = client;
+
+ final service = TodoService();
+ service.todoRepository = repo;
+
+ expect(await service.getTodos().then((value) => value.length), equals(1));
+ });
+
+ test('throws an exception if the http call completes with an error', () {
+ final client = MockClient();
+ final repo = HTTPTodoRepository();
+
+ // Use Mockito to return an unsuccessful response when it calls the
+ // provided http.Client.
+ when(client.get(Uri.parse('https://jsonplaceholder.typicode.com/todos')))
+ .thenAnswer((_) async => http.Response('Not Found', 404));
+
+ repo.client = client;
+
+ final service = TodoService();
+ service.todoRepository = repo;
+
+ expect(service.getTodos(), throwsException);
+ });
+}
-deleteAll() async {
- final db = await database;
- db.rawDelete("Delete * from Client");
+```
+
+This is the same process as before.
+We have all the unit tests we want.
+Let's run the following command.
+
+```sh
+flutter test --coverage
+```
+
+This will run the four tests.
+They should all pass.
+All that's left is testing the widgets.
+Let's do it!
+
+### 6.2 Widget testing
+
+To test our widgets,
+we need to pass the `TodoService`
+so we can mock it in our tests.
+Normally we would use a Provider to do this
+but this is a simple app,
+so there is no need to add complexity and third-party libraries.
+
+Let's do these changes.
+Head over to `lib/main.dart` and change the `TodoList` class.
+Like so.
+
+```dart
+class TodoList extends StatefulWidget {
+ final TodoService todoService;
+
+ const TodoList({super.key, required this.todoService});
+
+ @override
+ State createState() => _TodoListState();
+}
+```
+
+Now, we need to change the `MyApp` class
+to pass a `TodoService` instance to `TodoList`.
+It should look like this, now.
+
+```dart
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Todo App',
+ theme: ThemeData(
+ appBarTheme: const AppBarTheme(
+ backgroundColor: Colors.black,
+ foregroundColor: Colors.white,
+ )),
+ home: TodoList(todoService: TodoService()),
+ );
}
+}
+```
+
+Now we can test these widgets!
+Create a new directory `test/widget`
+and create a file named `widget_test.dart`.
+
+```dart
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+import 'package:mockito/annotations.dart';
+import 'package:demo_app/main.dart';
+
+
+@GenerateMocks([TodoService])
+void main() {
+}
```
+Run the following command.
-## Trouble-Shooting Section
+```sh
+flutter pub run build_runner build --delete-conflicting-outputs
+```
-The *first* time I tried to change the path of the Flutter folder so that it could be accessed by the terminal without having to use several paths, it **did not work**, every time I closed the terminal Flutter was no longer recognized.
+This will generate a `widget_test.mocks.dart` file
+with the generated mocks for `TodoService`.
+Now we are ready to test our first widget!
+Add the following test inside `main()`.
-`command not found: flutter`
+```dart
+import 'widget_test.mocks.dart';
-Whenever I used *this* command:
+@GenerateMocks([TodoService])
+void main() {
+ testWidgets('Check if appbar renders', (WidgetTester tester) async {
+ // Build our app and trigger a frame.
+ await tester.pumpWidget(const MyApp());
+ // Verify that the appbar renders
+ expect(find.text('todo item list'), findsOneWidget);
+ });
+}
+```
-`export PATH="$PATH:~/development/flutter/bin`
+We use the `testWidgets` function to test the widget.
+In turn, we get a `tester` object
+which allows us to perform actions.
+We initialize and create the widget by using `await test.pumpWidget(const MyApp())`.
+We then check if the app bar is rendered.
+To do this, we use the `find` class to find the widget by text
+and check if it was built in the widget tree.
+We then use a `Matcher` to make the assertion.
+In this case, we check if we `findOneWidget`.
-Then I started to notice that this command only worked for the current terminal, so it was not **permanent**.
-Even using the command that should change the path permanently Flutter was still **unrecognized**.
+If we run `flutter test --coverage`,
+we will see this test should pass.
-This would be the command that would make Flutter access through the terminal permanent:
+Let's now add a test to check if the list is rendered with a list of todos.
+Add the following test.
-`export PATH="$PATH:[PATH_TO_FLUTTER_GIT_DIRECTORY]/flutter/bin"`
+```dart
+ testWidgets('Check if item list is rendered', (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
-Changing the path to where we would have cloned Flutter's Git repository [PATH_TO_FLUTTER_GIT_DIRECTORY].
-And then using the command:
+ when(mockService.getTodos()).thenAnswer((_) async =>
+ [const Todo(userId: 1, id: 1, title: 'mocktitle', completed: true)]);
-`source ~/.bash_profile`
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
-What will help us refresh the terminal.
-But even so, every time I closed and opened the terminal again Flutter was no longer recognized.
-So I thought that maybe the best option would be to go to the document and edit it with the "export PATH" command but inside the document itself other than in the terminal.
+ // Expect the mocked todo item to be displayed
+ expect(find.text('mocktitle'), findsOneWidget);
+ });
+```
-So I went to Finder / Macintosh HD / Users / and to my user.
-How is a hidden file or document we must type **`cmd` + `shift` + `.`**
+In this test,
+we are instantiating a `MockTodoService`,
+specifying the return value of the `getTodos()`
+and then using it when creating a `TodoList` widget.
+We can't create `TodoList` by itself
+because it needs to be a child of `MaterialApp`.
+Hence why we use `MediaQuery` with `MaterialApp` which,
+in turn, creates a `TodoList` widget that we want to test.
+
+With `tester.pumpWidget()`,
+we instantiate the widget.
+This won't suffice, though.
+The widget needs to render any animations
+and run `initState` to fetch the todos item.
+For this, we add `await tester.pump()` with a specified duration.
+This schedules a frame
+and triggers a rebuild of the widget,
+running the clock by that amount.
+We only need `100 ms` in our case.
+
+After this,
+we assert if the rendered list contains a todo item with a title "mocktitle".
+
+Let's add another test.
+
+```dart
+ testWidgets('Navigating to the todo list directly and find empty widget array',
+ (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos()).thenAnswer((_) async => [
+ const Todo(userId: 1, id: 1, title: 'mocktitle', completed: true),
+ const Todo(userId: 1, id: 2, title: 'mocktitle2', completed: true),
+ ]);
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Navigating away
+ await tester.tap(find.byIcon((Icons.list)));
+ await tester.pumpAndSettle();
+
+ // Expect the todo list page to be shown
+ expect(find.text('completed todo list'), findsOneWidget);
+ });
+```
+
+In this test,
+we are rendering the `TodoList`,
+tapping on a todo item (thus marking it as `complete`)
+and then navigating to the done todo item list.
+For this,
+we use `tester.tap(find.byIcon((Icons.list)))`
+to find the button and tap it.
+We then use `tester.pumpAndSettle()`,
+which essentially waits for all the animations to complete.
+After this,
+we check if the done list screen is rendered in the widget tree.
+
+We can keep adding tests
+to cover the rest of the scenarios.
+Copy the following code
+and replace your existing tests to cover all edge cases.
+Your `main` function should now look like this.
+
+```dart
+@GenerateMocks([TodoService])
+void main() {
+ testWidgets('Check if appbar renders', (WidgetTester tester) async {
+ // Build our app and trigger a frame.
+ await tester.pumpWidget(const MyApp());
+
+ // Verify that the appbar renders
+ expect(find.text('todo item list'), findsOneWidget);
+ });
+
+ testWidgets('Check if item list is rendered', (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos()).thenAnswer((_) async =>
+ [const Todo(userId: 1, id: 1, title: 'mocktitle', completed: true)]);
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Expect the mocked todo item to be displayed
+ expect(find.text('mocktitle'), findsOneWidget);
+ });
+
+ testWidgets('Error should be displayed if the server returns error',
+ (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos())
+ .thenAnswer((_) async => throw Exception('Error getting todos.'));
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Expect the mocked todo item to be displayed
+ expect(find.text('Exception: Error getting todos.'), findsOneWidget);
+ });
+
+ testWidgets('Tapping on a todo item and navigating to the done list page.',
+ (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos()).thenAnswer((_) async => [
+ const Todo(userId: 1, id: 1, title: 'mocktitle', completed: true),
+ const Todo(userId: 1, id: 2, title: 'mocktitle2', completed: true),
+ ]);
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Tapping on a todo
+ await tester.tap(find.text('mocktitle'));
+ await tester.pumpAndSettle();
+
+ // Navigating away
+ await tester.tap(find.byIcon((Icons.list)));
+ await tester.pumpAndSettle();
+
+ // Expect the todo list page to be shown
+ expect(find.text('completed todo list'), findsOneWidget);
+ expect(find.text('todo item list'), findsNothing);
+ });
+
+ testWidgets('Navigating to the todo list directly and find empty widget array',
+ (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos()).thenAnswer((_) async => [
+ const Todo(userId: 1, id: 1, title: 'mocktitle', completed: true),
+ const Todo(userId: 1, id: 2, title: 'mocktitle2', completed: true),
+ ]);
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Navigating away
+ await tester.tap(find.byIcon((Icons.list)));
+ await tester.pumpAndSettle();
-![finder-view-showing-flutter-directory](https://user-images.githubusercontent.com/27420533/74145756-83e43d80-4bf7-11ea-94ca-f6be3cfac21b.png)
+ // Expect the todo list page to be shown
+ expect(find.text('completed todo list'), findsOneWidget);
+ });
+
+ testWidgets('Marking todo as done and then as undone',
+ (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos()).thenAnswer((_) async => [
+ const Todo(userId: 1, id: 1, title: 'mocktitle', completed: true),
+ const Todo(userId: 1, id: 2, title: 'mocktitle2', completed: true),
+ ]);
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Tap and untap
+ await tester.tap(find.text('mocktitle'));
+ await tester.pumpAndSettle();
+
+ await tester.tap(find.text('mocktitle'));
+ await tester.pumpAndSettle();
+
+ // Expect the todo list page to be shown
+ expect(find.text('mocktitle'), findsOneWidget);
+ });
+
+ testWidgets('Testing main mount',
+ (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos()).thenAnswer((_) async => [
+ const Todo(userId: 1, id: 1, title: 'mocktitle', completed: true),
+ const Todo(userId: 1, id: 2, title: 'mocktitle2', completed: true),
+ ]);
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Tap and untap
+ await tester.tap(find.text('mocktitle'));
+ await tester.pumpAndSettle();
-Then I opened my **`.zhs_profile`** file and typed the command:
+ await tester.tap(find.text('mocktitle'));
+ await tester.pumpAndSettle();
+
+ // Expect the todo list page to be shown
+ expect(find.text('mocktitle'), findsOneWidget);
+ });
+}
+```
+
+The final changes we ought to do is in the `main.dart` file.
+We can't directly test the `main()` function
+that runs the application.
+So, in order to get a real coverage report,
+add the following lines around the function.
+This way, when testing,
+the compiler skips this function,
+as it is not needed to be tested.
+
+```dart
+// coverage:ignore-start
+void main() {
+ runApp(const MyApp());
+}
+// coverage:ignore-end
+```
+
+### 6.3 Test coverage
+
+To get the test coverage,
+we are going to simply run three commands.
+However, firstly, if you are on MacOS,
+you need to install `lcov`.
+For this, run the following command
+to install it in your computer.
+
+```sh
+brew install lcov
+```
+
+Now, to get the coverage, run the following commands.
+
+```sh
+# Generate `coverage/lcov.info` file
+flutter test --coverage
+# Generate HTML report
+genhtml coverage/lcov.info -o coverage/html
+# Open the report
+open coverage/html/index.html
+```
-`export PATH=/Users/m/Documents/flutter/bin:$PATH`
+The generated HTML will create files
+inside the `coverage/` folder.
+Add it to your `.gitignore` file.
-This instructs my terminal environment to export an updated version of the `PATH` environment variable with `flutter/bin` path _prepended_ to the current `PATH` variable. (_i.e. add `flutter/bin` to the `PATH` so that my terminal knows where to find the `flutter` CLI_)
+Your browser should have opened a window,
+like so.
-Then I saved the file and now going to the terminal Flutter is already recognized and can be accessed without having to run any commands.
+
+Congratulations,
+you now have a fully tested application!
+Awesome job! ๐
-![flutter-command-output-truncated](https://user-images.githubusercontent.com/27420533/74146245-9a3ec900-4bf8-11ea-8dca-63b549552ff4.png)
+# Final remarks ๐
+In this document (if you actually read it all the way through ๐),
+you went from 0 to hero with Flutter.
+You learnt important principles and *applied* them
+to create your own app in just around 20 minutes!
+Give yourself a pat on the back!
-## How can you improve your learning in Flutter?
+If you wish to learn a bit more,
+take a look at this repo's `guides` folder
+to learn about [logging in with Firebase](./guides/login-firebase-tutorial.md)
+or [webviews in Flutter](./guides/webview-tutorial.md).
-- Get to know the Flutter platform vividly
-- Thoroughly acquaint with all the features of Flutter
-- Read through Flutterโs official documentation as it include easy examples that beginners would find helpful. The documents are seasoned with iOS and Android devices. The developers can easily interact with the present device
-- Including Google codelabs in the learning process
-- Adding the Flutter GitHub repository in the learning process
-- Opting for Googleโs free beginner Flutter learning course. Google provides a free course for learners.
+If you want to see more fully tested projects,
+check these out!
+- [flutter-todo-list-tutorial](https://github.com/dwyl/flutter-todo-list-tutorial)
+- [flutter-counter-example](https://github.com/dwyl/flutter-counter-example)
diff --git a/android/.gitignore b/android/.gitignore
deleted file mode 100644
index bc2100d..0000000
--- a/android/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-gradle-wrapper.jar
-/.gradle
-/captures/
-/gradlew
-/gradlew.bat
-/local.properties
-GeneratedPluginRegistrant.java
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
deleted file mode 100644
index 0705650..0000000
--- a/android/app/src/debug/AndroidManifest.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/android/app/src/main/kotlin/com/example/learn_flutter/MainActivity.kt b/android/app/src/main/kotlin/com/example/learn_flutter/MainActivity.kt
deleted file mode 100644
index 2fdc90e..0000000
--- a/android/app/src/main/kotlin/com/example/learn_flutter/MainActivity.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.example.learn_flutter
-
-import androidx.annotation.NonNull;
-import io.flutter.embedding.android.FlutterActivity
-import io.flutter.embedding.engine.FlutterEngine
-import io.flutter.plugins.GeneratedPluginRegistrant
-
-class MainActivity: FlutterActivity() {
- override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
- GeneratedPluginRegistrant.registerWith(flutterEngine);
- }
-}
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
deleted file mode 100644
index 00fa441..0000000
--- a/android/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
diff --git a/android/learn_flutter_android.iml b/android/learn_flutter_android.iml
deleted file mode 100644
index 1029d72..0000000
--- a/android/learn_flutter_android.iml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android/settings.gradle b/android/settings.gradle
deleted file mode 100644
index 5a2f14f..0000000
--- a/android/settings.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-include ':app'
-
-def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
-
-def plugins = new Properties()
-def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
-if (pluginsFile.exists()) {
- pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
-}
-
-plugins.each { name, path ->
- def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
- include ":$name"
- project(":$name").projectDir = pluginDirectory
-}
diff --git a/tdd_architeture/.gitignore b/demo_app/.gitignore
similarity index 65%
rename from tdd_architeture/.gitignore
rename to demo_app/.gitignore
index ae1f183..00e0e8c 100644
--- a/tdd_architeture/.gitignore
+++ b/demo_app/.gitignore
@@ -8,6 +8,8 @@
.buildlog/
.history
.svn/
+migrate_working_dir/
+coverage
# IntelliJ related
*.iml
@@ -22,6 +24,7 @@
# Flutter/Dart/Pub related
**/doc/api/
+**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
@@ -30,8 +33,13 @@
.pub/
/build/
-# Web related
-lib/generated_plugin_registrant.dart
+# Symbolication related
+app.*.symbols
-# Exceptions to above rules.
-!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/demo_app/.metadata b/demo_app/.metadata
new file mode 100644
index 0000000..2438210
--- /dev/null
+++ b/demo_app/.metadata
@@ -0,0 +1,45 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled.
+
+version:
+ revision: d9111f64021372856901a1fd5bfbc386cade3318
+ channel: stable
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ base_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ - platform: android
+ create_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ base_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ - platform: ios
+ create_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ base_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ - platform: linux
+ create_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ base_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ - platform: macos
+ create_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ base_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ - platform: web
+ create_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ base_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ - platform: windows
+ create_revision: d9111f64021372856901a1fd5bfbc386cade3318
+ base_revision: d9111f64021372856901a1fd5bfbc386cade3318
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/demo_app/analysis_options.yaml b/demo_app/analysis_options.yaml
new file mode 100644
index 0000000..61b6c4d
--- /dev/null
+++ b/demo_app/analysis_options.yaml
@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at
+ # https://dart-lang.github.io/linter/lints/index.html.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/demo_app/android/.gitignore b/demo_app/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/demo_app/android/.gitignore
@@ -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
diff --git a/android/app/build.gradle b/demo_app/android/app/build.gradle
similarity index 72%
rename from android/app/build.gradle
rename to demo_app/android/app/build.gradle
index 5f4bb2e..ab4af1d 100644
--- a/android/app/build.gradle
+++ b/demo_app/android/app/build.gradle
@@ -26,24 +26,31 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
- compileSdkVersion 28
+ compileSdkVersion flutter.compileSdkVersion
+ ndkVersion flutter.ndkVersion
- sourceSets {
- main.java.srcDirs += 'src/main/kotlin'
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
}
- lintOptions {
- disable 'InvalidPackage'
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId "com.example.learn_flutter"
- minSdkVersion 16
- targetSdkVersion 28
+ applicationId "com.example.demo_app"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
+ minSdkVersion flutter.minSdkVersion
+ targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -61,7 +68,4 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'androidx.test:runner:1.1.1'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
diff --git a/tdd_architeture/android/app/src/profile/AndroidManifest.xml b/demo_app/android/app/src/debug/AndroidManifest.xml
similarity index 53%
rename from tdd_architeture/android/app/src/profile/AndroidManifest.xml
rename to demo_app/android/app/src/debug/AndroidManifest.xml
index 476c23f..22b7808 100644
--- a/tdd_architeture/android/app/src/profile/AndroidManifest.xml
+++ b/demo_app/android/app/src/debug/AndroidManifest.xml
@@ -1,6 +1,7 @@
-
diff --git a/android/app/src/main/AndroidManifest.xml b/demo_app/android/app/src/main/AndroidManifest.xml
similarity index 60%
rename from android/app/src/main/AndroidManifest.xml
rename to demo_app/android/app/src/main/AndroidManifest.xml
index dbfd242..9a3af4d 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/demo_app/android/app/src/main/AndroidManifest.xml
@@ -1,21 +1,25 @@
-
-
+
+
+
diff --git a/demo_app/android/app/src/main/kotlin/com/example/demo_app/MainActivity.kt b/demo_app/android/app/src/main/kotlin/com/example/demo_app/MainActivity.kt
new file mode 100644
index 0000000..6a05513
--- /dev/null
+++ b/demo_app/android/app/src/main/kotlin/com/example/demo_app/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.example.demo_app
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/tdd_architeture/android/app/src/main/res/drawable/launch_background.xml b/demo_app/android/app/src/main/res/drawable-v21/launch_background.xml
similarity index 86%
rename from tdd_architeture/android/app/src/main/res/drawable/launch_background.xml
rename to demo_app/android/app/src/main/res/drawable-v21/launch_background.xml
index 304732f..f74085f 100644
--- a/tdd_architeture/android/app/src/main/res/drawable/launch_background.xml
+++ b/demo_app/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -1,7 +1,7 @@
-
+
+
+
+
+
diff --git a/demo_app/android/app/src/main/res/values/styles.xml b/demo_app/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/demo_app/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/profile/AndroidManifest.xml b/demo_app/android/app/src/profile/AndroidManifest.xml
similarity index 53%
rename from android/app/src/profile/AndroidManifest.xml
rename to demo_app/android/app/src/profile/AndroidManifest.xml
index 0705650..22b7808 100644
--- a/android/app/src/profile/AndroidManifest.xml
+++ b/demo_app/android/app/src/profile/AndroidManifest.xml
@@ -1,6 +1,7 @@
-
diff --git a/android/build.gradle b/demo_app/android/build.gradle
similarity index 76%
rename from android/build.gradle
rename to demo_app/android/build.gradle
index 3100ad2..83ae220 100644
--- a/android/build.gradle
+++ b/demo_app/android/build.gradle
@@ -1,12 +1,12 @@
buildscript {
- ext.kotlin_version = '1.3.50'
+ ext.kotlin_version = '1.6.10'
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.0'
+ classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -14,7 +14,7 @@ buildscript {
allprojects {
repositories {
google()
- jcenter()
+ mavenCentral()
}
}
diff --git a/android/gradle.properties b/demo_app/android/gradle.properties
similarity index 78%
rename from android/gradle.properties
rename to demo_app/android/gradle.properties
index 38c8d45..94adc3a 100644
--- a/android/gradle.properties
+++ b/demo_app/android/gradle.properties
@@ -1,4 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
-android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/demo_app/android/gradle/wrapper/gradle-wrapper.properties
similarity index 79%
rename from android/gradle/wrapper/gradle-wrapper.properties
rename to demo_app/android/gradle/wrapper/gradle-wrapper.properties
index 296b146..cb24abd 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/demo_app/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
diff --git a/demo_app/android/settings.gradle b/demo_app/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/demo_app/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/ios/.gitignore b/demo_app/ios/.gitignore
similarity index 95%
rename from ios/.gitignore
rename to demo_app/ios/.gitignore
index e96ef60..7a7f987 100644
--- a/ios/.gitignore
+++ b/demo_app/ios/.gitignore
@@ -1,3 +1,4 @@
+**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
@@ -18,6 +19,7 @@ Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
+Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/demo_app/ios/Flutter/AppFrameworkInfo.plist
similarity index 91%
rename from ios/Flutter/AppFrameworkInfo.plist
rename to demo_app/ios/Flutter/AppFrameworkInfo.plist
index 6b4c0f7..9625e10 100644
--- a/ios/Flutter/AppFrameworkInfo.plist
+++ b/demo_app/ios/Flutter/AppFrameworkInfo.plist
@@ -3,7 +3,7 @@
CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
+ en
CFBundleExecutable
App
CFBundleIdentifier
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 8.0
+ 11.0
diff --git a/ios/Flutter/Debug.xcconfig b/demo_app/ios/Flutter/Debug.xcconfig
similarity index 100%
rename from ios/Flutter/Debug.xcconfig
rename to demo_app/ios/Flutter/Debug.xcconfig
diff --git a/ios/Flutter/Release.xcconfig b/demo_app/ios/Flutter/Release.xcconfig
similarity index 100%
rename from ios/Flutter/Release.xcconfig
rename to demo_app/ios/Flutter/Release.xcconfig
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/demo_app/ios/Runner.xcodeproj/project.pbxproj
similarity index 85%
rename from ios/Runner.xcodeproj/project.pbxproj
rename to demo_app/ios/Runner.xcodeproj/project.pbxproj
index 87497d7..1e301e0 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/demo_app/ios/Runner.xcodeproj/project.pbxproj
@@ -3,17 +3,13 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 46;
+ objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
- 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
- 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
- 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
- 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -26,8 +22,6 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
- 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
- 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -38,13 +32,11 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
- 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
@@ -57,8 +49,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
- 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -68,9 +58,7 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
- 3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
- 9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
@@ -102,7 +90,6 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
- 97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
@@ -111,13 +98,6 @@
path = Runner;
sourceTree = "";
};
- 97C146F11CF9000F007C117D /* Supporting Files */ = {
- isa = PBXGroup;
- children = (
- );
- name = "Supporting Files";
- sourceTree = "";
- };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -147,7 +127,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1020;
+ LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@@ -157,7 +137,7 @@
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
- compatibilityVersion = "Xcode 3.2";
+ compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -201,7 +181,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
@@ -253,7 +233,6 @@
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -293,7 +272,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -310,17 +289,12 @@
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- LIBRARY_SEARCH_PATHS = (
+ LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
- "$(PROJECT_DIR)/Flutter",
+ "@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.example.learnFlutter;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.demoApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -330,7 +304,6 @@
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -376,7 +349,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -386,7 +359,6 @@
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -426,11 +398,12 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
- SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@@ -444,17 +417,12 @@
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- LIBRARY_SEARCH_PATHS = (
+ LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
- "$(PROJECT_DIR)/Flutter",
+ "@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.example.learnFlutter;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.demoApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -471,17 +439,12 @@
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- LIBRARY_SEARCH_PATHS = (
+ LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
- "$(PROJECT_DIR)/Flutter",
+ "@executable_path/Frameworks",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.example.learnFlutter;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.demoApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
diff --git a/tdd_architeture/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/demo_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
similarity index 71%
rename from tdd_architeture/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
rename to demo_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
index 1d526a1..919434a 100644
--- a/tdd_architeture/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ b/demo_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -2,6 +2,6 @@
+ location = "self:">
diff --git a/demo_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/demo_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/demo_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/demo_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/demo_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/demo_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/demo_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
similarity index 95%
rename from ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
rename to demo_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a28140c..c87d15a 100644
--- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/demo_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
-
-
-
-
+
+
-
-
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/demo_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/demo_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/demo_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/ios/Runner/AppDelegate.swift b/demo_app/ios/Runner/AppDelegate.swift
similarity index 100%
rename from ios/Runner/AppDelegate.swift
rename to demo_app/ios/Runner/AppDelegate.swift
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
rename to demo_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/demo_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
similarity index 100%
rename from ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
rename to demo_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/demo_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
rename to demo_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/demo_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
rename to demo_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/demo_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
similarity index 100%
rename from ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
rename to demo_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/demo_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
similarity index 100%
rename from ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
rename to demo_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/demo_app/ios/Runner/Base.lproj/LaunchScreen.storyboard
similarity index 100%
rename from ios/Runner/Base.lproj/LaunchScreen.storyboard
rename to demo_app/ios/Runner/Base.lproj/LaunchScreen.storyboard
diff --git a/ios/Runner/Base.lproj/Main.storyboard b/demo_app/ios/Runner/Base.lproj/Main.storyboard
similarity index 100%
rename from ios/Runner/Base.lproj/Main.storyboard
rename to demo_app/ios/Runner/Base.lproj/Main.storyboard
diff --git a/tdd_architeture/ios/Runner/Info.plist b/demo_app/ios/Runner/Info.plist
similarity index 87%
rename from tdd_architeture/ios/Runner/Info.plist
rename to demo_app/ios/Runner/Info.plist
index 4401fca..dcae618 100644
--- a/tdd_architeture/ios/Runner/Info.plist
+++ b/demo_app/ios/Runner/Info.plist
@@ -4,6 +4,8 @@
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Demo App
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
@@ -11,7 +13,7 @@
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- clean_architeture_tdd
+ demo_app
CFBundlePackageType
APPL
CFBundleShortVersionString
@@ -41,5 +43,9 @@
UIViewControllerBasedStatusBarAppearance
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
diff --git a/ios/Runner/Runner-Bridging-Header.h b/demo_app/ios/Runner/Runner-Bridging-Header.h
similarity index 100%
rename from ios/Runner/Runner-Bridging-Header.h
rename to demo_app/ios/Runner/Runner-Bridging-Header.h
diff --git a/demo_app/lib/main.dart b/demo_app/lib/main.dart
new file mode 100644
index 0000000..dcb0aac
--- /dev/null
+++ b/demo_app/lib/main.dart
@@ -0,0 +1,140 @@
+import 'package:demo_app/models/todo.dart';
+import 'package:flutter/material.dart';
+
+import 'services/todoService.dart';
+
+// coverage:ignore-start
+void main() {
+ runApp(const MyApp());
+}
+// coverage:ignore-end
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Todo App',
+ theme: ThemeData(
+ appBarTheme: const AppBarTheme(
+ backgroundColor: Colors.black,
+ foregroundColor: Colors.white,
+ )),
+ home: TodoList(todoService: TodoService()),
+ );
+ }
+}
+
+class TodoList extends StatefulWidget {
+ final TodoService todoService;
+
+ const TodoList({super.key, required this.todoService});
+
+ @override
+ State createState() => _TodoListState();
+}
+
+class _TodoListState extends State {
+ late Future> futureTodosList;
+ final Set _doneList = {};
+
+ void _pushCompleted() {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (context) {
+ final tiles = _doneList.map(
+ (todo) {
+ return ListTile(
+ title: Text(
+ todo.title,
+ style: const TextStyle(fontSize: 18),
+ ),
+ );
+ },
+ );
+ final divided = tiles.isNotEmpty
+ ? ListTile.divideTiles(
+ context: context,
+ tiles: tiles,
+ ).toList()
+ : [];
+
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('completed todo list'),
+ ),
+ body: ListView(children: divided),
+ );
+ },
+ ),
+ );
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ futureTodosList = widget.todoService.getTodos();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('todo item list'),
+ actions: [
+ IconButton(
+ icon: const Icon(Icons.list),
+ onPressed: _pushCompleted,
+ tooltip: 'completed todo list',
+ ),
+ ],
+ ),
+ body: FutureBuilder>(
+ future: futureTodosList,
+ builder: (context, snapshot) {
+ if (snapshot.hasData) {
+ final todolist = snapshot.data!;
+
+ return ListView.builder(
+ itemCount: todolist.length,
+ padding: const EdgeInsets.all(16.0),
+ itemBuilder: (context, i) {
+ final index = i ~/ 2;
+ final todoObj = todolist[index];
+
+ if (i.isOdd) return const Divider();
+
+ final completed = _doneList.contains(todoObj);
+
+ return ListTile(
+ title: Text(
+ todoObj.title,
+ style: TextStyle(
+ fontSize: 18,
+ decoration: completed
+ ? TextDecoration.lineThrough
+ : TextDecoration.none),
+ ),
+ onTap: (() {
+ setState(() {
+ if (completed) {
+ _doneList.remove(todoObj);
+ } else {
+ _doneList.add(todoObj);
+ }
+ });
+ }),
+ );
+ },
+ );
+ } else if (snapshot.hasError) {
+ return Text('${snapshot.error}');
+ }
+
+ // By default, show a loading spinner.
+ return const CircularProgressIndicator();
+ },
+ ));
+ }
+}
diff --git a/demo_app/lib/models/todo.dart b/demo_app/lib/models/todo.dart
new file mode 100644
index 0000000..01a271c
--- /dev/null
+++ b/demo_app/lib/models/todo.dart
@@ -0,0 +1,22 @@
+class Todo {
+ final int userId;
+ final int id;
+ final String title;
+ final bool completed;
+
+ const Todo({
+ required this.userId,
+ required this.id,
+ required this.title,
+ required this.completed,
+ });
+
+ factory Todo.fromJson(Map json) {
+ return Todo(
+ userId: json['userId'],
+ id: json['id'],
+ title: json['title'],
+ completed: json['completed'],
+ );
+ }
+}
diff --git a/demo_app/lib/repository/todoRepository.dart b/demo_app/lib/repository/todoRepository.dart
new file mode 100644
index 0000000..5ed24d0
--- /dev/null
+++ b/demo_app/lib/repository/todoRepository.dart
@@ -0,0 +1,28 @@
+import 'dart:convert';
+
+import 'package:demo_app/models/todo.dart';
+import 'package:http/http.dart' show Client;
+
+abstract class TodoRepository {
+ Future> getTodos();
+}
+
+class HTTPTodoRepository implements TodoRepository {
+ Client client = Client();
+
+ @override
+ Future> getTodos() async {
+ final response = await client
+ .get(Uri.parse("https://jsonplaceholder.typicode.com/todos"));
+
+ if (response.statusCode == 200) {
+ Iterable l = json.decode(response.body);
+ List todos =
+ List.from(l.map((model) => Todo.fromJson(model)));
+
+ return todos;
+ } else {
+ throw Exception('Failed to load Todo\'s.');
+ }
+ }
+}
diff --git a/demo_app/lib/services/todoService.dart b/demo_app/lib/services/todoService.dart
new file mode 100644
index 0000000..7f2f80e
--- /dev/null
+++ b/demo_app/lib/services/todoService.dart
@@ -0,0 +1,8 @@
+import 'package:demo_app/models/todo.dart';
+import 'package:demo_app/repository/todoRepository.dart';
+
+class TodoService {
+ TodoRepository todoRepository = HTTPTodoRepository();
+
+ Future> getTodos() => todoRepository.getTodos();
+}
diff --git a/demo_app/linux/.gitignore b/demo_app/linux/.gitignore
new file mode 100644
index 0000000..d3896c9
--- /dev/null
+++ b/demo_app/linux/.gitignore
@@ -0,0 +1 @@
+flutter/ephemeral
diff --git a/demo_app/linux/CMakeLists.txt b/demo_app/linux/CMakeLists.txt
new file mode 100644
index 0000000..6efd888
--- /dev/null
+++ b/demo_app/linux/CMakeLists.txt
@@ -0,0 +1,138 @@
+# Project-level configuration.
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+# The name of the executable created for the application. Change this to change
+# the on-disk name of your application.
+set(BINARY_NAME "demo_app")
+# The unique GTK application identifier for this application. See:
+# https://wiki.gnome.org/HowDoI/ChooseApplicationID
+set(APPLICATION_ID "com.example.demo_app")
+
+# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
+# versions of CMake.
+cmake_policy(SET CMP0063 NEW)
+
+# Load bundled libraries from the lib/ directory relative to the binary.
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Root filesystem for cross-building.
+if(FLUTTER_TARGET_PLATFORM_SYSROOT)
+ set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
+ set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
+ set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+ set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+endif()
+
+# Define build configuration options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE
+ STRING "Flutter build mode" FORCE)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+ "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+#
+# Be cautious about adding new options here, as plugins use this function by
+# default. In most cases, you should add new options to specific targets instead
+# of modifying this function.
+function(APPLY_STANDARD_SETTINGS TARGET)
+ target_compile_features(${TARGET} PUBLIC cxx_std_14)
+ target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+ target_compile_options(${TARGET} PRIVATE "$<$>:-O3>")
+ target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>")
+endfunction()
+
+# Flutter library and tool build rules.
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# Define the application target. To change its name, change BINARY_NAME above,
+# not the value here, or `flutter run` will no longer work.
+#
+# Any new source files that you add to the application should be added here.
+add_executable(${BINARY_NAME}
+ "main.cc"
+ "my_application.cc"
+ "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
+apply_standard_settings(${BINARY_NAME})
+
+# Add dependency libraries. Add any application-specific dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+
+# Run the Flutter tool portions of the build. This must not be removed.
+add_dependencies(${BINARY_NAME} flutter_assemble)
+
+# Only the install-generated bundle's copy of the executable will launch
+# correctly, since the resources must in the right relative locations. To avoid
+# people trying to run the unbundled copy, put it in a subdirectory instead of
+# the default top-level location.
+set_target_properties(${BINARY_NAME}
+ PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
+)
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+# Start with a clean build bundle directory every time.
+install(CODE "
+ file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
+ " COMPONENT Runtime)
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+
+foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
+ install(FILES "${bundled_library}"
+ DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endforeach(bundled_library)
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+ file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+ " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+ DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+ install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endif()
diff --git a/demo_app/linux/flutter/CMakeLists.txt b/demo_app/linux/flutter/CMakeLists.txt
new file mode 100644
index 0000000..d5bd016
--- /dev/null
+++ b/demo_app/linux/flutter/CMakeLists.txt
@@ -0,0 +1,88 @@
+# This file controls Flutter-level build steps. It should not be edited.
+cmake_minimum_required(VERSION 3.10)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+
+# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
+# which isn't available in 3.10.
+function(list_prepend LIST_NAME PREFIX)
+ set(NEW_LIST "")
+ foreach(element ${${LIST_NAME}})
+ list(APPEND NEW_LIST "${PREFIX}${element}")
+ endforeach(element)
+ set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
+endfunction()
+
+# === Flutter Library ===
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
+
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+ "fl_basic_message_channel.h"
+ "fl_binary_codec.h"
+ "fl_binary_messenger.h"
+ "fl_dart_project.h"
+ "fl_engine.h"
+ "fl_json_message_codec.h"
+ "fl_json_method_codec.h"
+ "fl_message_codec.h"
+ "fl_method_call.h"
+ "fl_method_channel.h"
+ "fl_method_codec.h"
+ "fl_method_response.h"
+ "fl_plugin_registrar.h"
+ "fl_plugin_registry.h"
+ "fl_standard_message_codec.h"
+ "fl_standard_method_codec.h"
+ "fl_string_codec.h"
+ "fl_value.h"
+ "fl_view.h"
+ "flutter_linux.h"
+)
+list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+ "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+target_link_libraries(flutter INTERFACE
+ PkgConfig::GTK
+ PkgConfig::GLIB
+ PkgConfig::GIO
+)
+add_dependencies(flutter flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+ OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+ ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+ COMMAND ${CMAKE_COMMAND} -E env
+ ${FLUTTER_TOOL_ENVIRONMENT}
+ "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+ ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
+ VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+ "${FLUTTER_LIBRARY}"
+ ${FLUTTER_LIBRARY_HEADERS}
+)
diff --git a/demo_app/linux/flutter/generated_plugin_registrant.cc b/demo_app/linux/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..e71a16d
--- /dev/null
+++ b/demo_app/linux/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,11 @@
+//
+// Generated file. Do not edit.
+//
+
+// clang-format off
+
+#include "generated_plugin_registrant.h"
+
+
+void fl_register_plugins(FlPluginRegistry* registry) {
+}
diff --git a/demo_app/linux/flutter/generated_plugin_registrant.h b/demo_app/linux/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..e0f0a47
--- /dev/null
+++ b/demo_app/linux/flutter/generated_plugin_registrant.h
@@ -0,0 +1,15 @@
+//
+// Generated file. Do not edit.
+//
+
+// clang-format off
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include
+
+// Registers Flutter plugins.
+void fl_register_plugins(FlPluginRegistry* registry);
+
+#endif // GENERATED_PLUGIN_REGISTRANT_
diff --git a/demo_app/linux/flutter/generated_plugins.cmake b/demo_app/linux/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..2e1de87
--- /dev/null
+++ b/demo_app/linux/flutter/generated_plugins.cmake
@@ -0,0 +1,23 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+ target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/demo_app/linux/main.cc b/demo_app/linux/main.cc
new file mode 100644
index 0000000..e7c5c54
--- /dev/null
+++ b/demo_app/linux/main.cc
@@ -0,0 +1,6 @@
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+ g_autoptr(MyApplication) app = my_application_new();
+ return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/demo_app/linux/my_application.cc b/demo_app/linux/my_application.cc
new file mode 100644
index 0000000..c253057
--- /dev/null
+++ b/demo_app/linux/my_application.cc
@@ -0,0 +1,104 @@
+#include "my_application.h"
+
+#include
+#ifdef GDK_WINDOWING_X11
+#include
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+ GtkApplication parent_instance;
+ char** dart_entrypoint_arguments;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+ MyApplication* self = MY_APPLICATION(application);
+ GtkWindow* window =
+ GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+
+ // Use a header bar when running in GNOME as this is the common style used
+ // by applications and is the setup most users will be using (e.g. Ubuntu
+ // desktop).
+ // If running on X and not using GNOME then just use a traditional title bar
+ // in case the window manager does more exotic layout, e.g. tiling.
+ // If running on Wayland assume the header bar will work (may need changing
+ // if future cases occur).
+ gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+ GdkScreen* screen = gtk_window_get_screen(window);
+ if (GDK_IS_X11_SCREEN(screen)) {
+ const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+ if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+ use_header_bar = FALSE;
+ }
+ }
+#endif
+ if (use_header_bar) {
+ GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+ gtk_widget_show(GTK_WIDGET(header_bar));
+ gtk_header_bar_set_title(header_bar, "demo_app");
+ gtk_header_bar_set_show_close_button(header_bar, TRUE);
+ gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+ } else {
+ gtk_window_set_title(window, "demo_app");
+ }
+
+ gtk_window_set_default_size(window, 1280, 720);
+ gtk_widget_show(GTK_WIDGET(window));
+
+ g_autoptr(FlDartProject) project = fl_dart_project_new();
+ fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
+
+ FlView* view = fl_view_new(project);
+ gtk_widget_show(GTK_WIDGET(view));
+ gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+ fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+
+ gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+// Implements GApplication::local_command_line.
+static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {
+ MyApplication* self = MY_APPLICATION(application);
+ // Strip out the first argument as it is the binary name.
+ self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
+
+ g_autoptr(GError) error = nullptr;
+ if (!g_application_register(application, nullptr, &error)) {
+ g_warning("Failed to register: %s", error->message);
+ *exit_status = 1;
+ return TRUE;
+ }
+
+ g_application_activate(application);
+ *exit_status = 0;
+
+ return TRUE;
+}
+
+// Implements GObject::dispose.
+static void my_application_dispose(GObject* object) {
+ MyApplication* self = MY_APPLICATION(object);
+ g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
+ G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+ G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+ G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
+ G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+ return MY_APPLICATION(g_object_new(my_application_get_type(),
+ "application-id", APPLICATION_ID,
+ "flags", G_APPLICATION_NON_UNIQUE,
+ nullptr));
+}
diff --git a/demo_app/linux/my_application.h b/demo_app/linux/my_application.h
new file mode 100644
index 0000000..72271d5
--- /dev/null
+++ b/demo_app/linux/my_application.h
@@ -0,0 +1,18 @@
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+ GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif // FLUTTER_MY_APPLICATION_H_
diff --git a/demo_app/macos/.gitignore b/demo_app/macos/.gitignore
new file mode 100644
index 0000000..746adbb
--- /dev/null
+++ b/demo_app/macos/.gitignore
@@ -0,0 +1,7 @@
+# Flutter-related
+**/Flutter/ephemeral/
+**/Pods/
+
+# Xcode-related
+**/dgph
+**/xcuserdata/
diff --git a/demo_app/macos/Flutter/Flutter-Debug.xcconfig b/demo_app/macos/Flutter/Flutter-Debug.xcconfig
new file mode 100644
index 0000000..c2efd0b
--- /dev/null
+++ b/demo_app/macos/Flutter/Flutter-Debug.xcconfig
@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/demo_app/macos/Flutter/Flutter-Release.xcconfig b/demo_app/macos/Flutter/Flutter-Release.xcconfig
new file mode 100644
index 0000000..c2efd0b
--- /dev/null
+++ b/demo_app/macos/Flutter/Flutter-Release.xcconfig
@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/demo_app/macos/Flutter/GeneratedPluginRegistrant.swift b/demo_app/macos/Flutter/GeneratedPluginRegistrant.swift
new file mode 100644
index 0000000..cccf817
--- /dev/null
+++ b/demo_app/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -0,0 +1,10 @@
+//
+// Generated file. Do not edit.
+//
+
+import FlutterMacOS
+import Foundation
+
+
+func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+}
diff --git a/demo_app/macos/Runner.xcodeproj/project.pbxproj b/demo_app/macos/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..db71855
--- /dev/null
+++ b/demo_app/macos/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,572 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 51;
+ objects = {
+
+/* Begin PBXAggregateTarget section */
+ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
+ isa = PBXAggregateTarget;
+ buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
+ buildPhases = (
+ 33CC111E2044C6BF0003C045 /* ShellScript */,
+ );
+ dependencies = (
+ );
+ name = "Flutter Assemble";
+ productName = FLX;
+ };
+/* End PBXAggregateTarget section */
+
+/* Begin PBXBuildFile section */
+ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
+ 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
+ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
+ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
+ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 33CC111A2044C6BA0003C045;
+ remoteInfo = FLX;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 33CC110E2044A8840003C045 /* Bundle Framework */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Bundle Framework";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; };
+ 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; };
+ 33CC10ED2044A3C60003C045 /* demo_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "demo_app.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; };
+ 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
+ 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; };
+ 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; };
+ 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; };
+ 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; };
+ 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; };
+ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; };
+ 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; };
+ 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 33CC10EA2044A3C60003C045 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 33BA886A226E78AF003329D5 /* Configs */ = {
+ isa = PBXGroup;
+ children = (
+ 33E5194F232828860026EE4D /* AppInfo.xcconfig */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
+ );
+ path = Configs;
+ sourceTree = "";
+ };
+ 33CC10E42044A3C60003C045 = {
+ isa = PBXGroup;
+ children = (
+ 33FAB671232836740065AC1E /* Runner */,
+ 33CEB47122A05771004F2AC0 /* Flutter */,
+ 33CC10EE2044A3C60003C045 /* Products */,
+ D73912EC22F37F3D000D13A0 /* Frameworks */,
+ );
+ sourceTree = "";
+ };
+ 33CC10EE2044A3C60003C045 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 33CC10ED2044A3C60003C045 /* demo_app.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 33CC11242044D66E0003C045 /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ 33CC10F22044A3C60003C045 /* Assets.xcassets */,
+ 33CC10F42044A3C60003C045 /* MainMenu.xib */,
+ 33CC10F72044A3C60003C045 /* Info.plist */,
+ );
+ name = Resources;
+ path = ..;
+ sourceTree = "";
+ };
+ 33CEB47122A05771004F2AC0 /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
+ 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
+ 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
+ 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
+ );
+ path = Flutter;
+ sourceTree = "";
+ };
+ 33FAB671232836740065AC1E /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 33CC10F02044A3C60003C045 /* AppDelegate.swift */,
+ 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
+ 33E51913231747F40026EE4D /* DebugProfile.entitlements */,
+ 33E51914231749380026EE4D /* Release.entitlements */,
+ 33CC11242044D66E0003C045 /* Resources */,
+ 33BA886A226E78AF003329D5 /* Configs */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+ D73912EC22F37F3D000D13A0 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 33CC10EC2044A3C60003C045 /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ 33CC10E92044A3C60003C045 /* Sources */,
+ 33CC10EA2044A3C60003C045 /* Frameworks */,
+ 33CC10EB2044A3C60003C045 /* Resources */,
+ 33CC110E2044A8840003C045 /* Bundle Framework */,
+ 3399D490228B24CF009A79C7 /* ShellScript */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 33CC11202044C79F0003C045 /* PBXTargetDependency */,
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 33CC10ED2044A3C60003C045 /* demo_app.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 33CC10E52044A3C60003C045 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 0920;
+ LastUpgradeCheck = 1300;
+ ORGANIZATIONNAME = "";
+ TargetAttributes = {
+ 33CC10EC2044A3C60003C045 = {
+ CreatedOnToolsVersion = 9.2;
+ LastSwiftMigration = 1100;
+ ProvisioningStyle = Automatic;
+ SystemCapabilities = {
+ com.apple.Sandbox = {
+ enabled = 1;
+ };
+ };
+ };
+ 33CC111A2044C6BA0003C045 = {
+ CreatedOnToolsVersion = 9.2;
+ ProvisioningStyle = Manual;
+ };
+ };
+ };
+ buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 33CC10E42044A3C60003C045;
+ productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 33CC10EC2044A3C60003C045 /* Runner */,
+ 33CC111A2044C6BA0003C045 /* Flutter Assemble */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 33CC10EB2044A3C60003C045 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
+ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 3399D490228B24CF009A79C7 /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
+ };
+ 33CC111E2044C6BF0003C045 /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ Flutter/ephemeral/FlutterInputs.xcfilelist,
+ );
+ inputPaths = (
+ Flutter/ephemeral/tripwire,
+ );
+ outputFileListPaths = (
+ Flutter/ephemeral/FlutterOutputs.xcfilelist,
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 33CC10E92044A3C60003C045 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
+ 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
+ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
+ targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 33CC10F52044A3C60003C045 /* Base */,
+ );
+ name = MainMenu.xib;
+ path = Runner;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 338D0CE9231458BD00FA5F75 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.11;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = macosx;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ };
+ name = Profile;
+ };
+ 338D0CEA231458BD00FA5F75 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Profile;
+ };
+ 338D0CEB231458BD00FA5F75 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Manual;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Profile;
+ };
+ 33CC10F92044A3C60003C045 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.11;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = macosx;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 33CC10FA2044A3C60003C045 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.11;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = macosx;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ };
+ name = Release;
+ };
+ 33CC10FC2044A3C60003C045 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 33CC10FD2044A3C60003C045 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+ 33CC111C2044C6BA0003C045 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Manual;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ 33CC111D2044C6BA0003C045 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33CC10F92044A3C60003C045 /* Debug */,
+ 33CC10FA2044A3C60003C045 /* Release */,
+ 338D0CE9231458BD00FA5F75 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33CC10FC2044A3C60003C045 /* Debug */,
+ 33CC10FD2044A3C60003C045 /* Release */,
+ 338D0CEA231458BD00FA5F75 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33CC111C2044C6BA0003C045 /* Debug */,
+ 33CC111D2044C6BA0003C045 /* Release */,
+ 338D0CEB231458BD00FA5F75 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 33CC10E52044A3C60003C045 /* Project object */;
+}
diff --git a/demo_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/demo_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/demo_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/tdd_architeture/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/demo_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
similarity index 83%
rename from tdd_architeture/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
rename to demo_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a28140c..6977e7e 100644
--- a/tdd_architeture/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/demo_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
@@ -27,19 +27,17 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
-
-
-
-
+
+
-
-
diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/demo_app/macos/Runner.xcworkspace/contents.xcworkspacedata
similarity index 100%
rename from ios/Runner.xcworkspace/contents.xcworkspacedata
rename to demo_app/macos/Runner.xcworkspace/contents.xcworkspacedata
diff --git a/demo_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/demo_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/demo_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/demo_app/macos/Runner/AppDelegate.swift b/demo_app/macos/Runner/AppDelegate.swift
new file mode 100644
index 0000000..d53ef64
--- /dev/null
+++ b/demo_app/macos/Runner/AppDelegate.swift
@@ -0,0 +1,9 @@
+import Cocoa
+import FlutterMacOS
+
+@NSApplicationMain
+class AppDelegate: FlutterAppDelegate {
+ override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
+ return true
+ }
+}
diff --git a/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..a2ec33f
--- /dev/null
+++ b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+ "images" : [
+ {
+ "size" : "16x16",
+ "idiom" : "mac",
+ "filename" : "app_icon_16.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "16x16",
+ "idiom" : "mac",
+ "filename" : "app_icon_32.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "32x32",
+ "idiom" : "mac",
+ "filename" : "app_icon_32.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "32x32",
+ "idiom" : "mac",
+ "filename" : "app_icon_64.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "128x128",
+ "idiom" : "mac",
+ "filename" : "app_icon_128.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "128x128",
+ "idiom" : "mac",
+ "filename" : "app_icon_256.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "256x256",
+ "idiom" : "mac",
+ "filename" : "app_icon_256.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "256x256",
+ "idiom" : "mac",
+ "filename" : "app_icon_512.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "512x512",
+ "idiom" : "mac",
+ "filename" : "app_icon_512.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "512x512",
+ "idiom" : "mac",
+ "filename" : "app_icon_1024.png",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
new file mode 100644
index 0000000..82b6f9d
Binary files /dev/null and b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ
diff --git a/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
new file mode 100644
index 0000000..13b35eb
Binary files /dev/null and b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ
diff --git a/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
new file mode 100644
index 0000000..0a3f5fa
Binary files /dev/null and b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ
diff --git a/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
new file mode 100644
index 0000000..bdb5722
Binary files /dev/null and b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ
diff --git a/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
new file mode 100644
index 0000000..f083318
Binary files /dev/null and b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ
diff --git a/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
new file mode 100644
index 0000000..326c0e7
Binary files /dev/null and b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ
diff --git a/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
new file mode 100644
index 0000000..2f1632c
Binary files /dev/null and b/demo_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ
diff --git a/demo_app/macos/Runner/Base.lproj/MainMenu.xib b/demo_app/macos/Runner/Base.lproj/MainMenu.xib
new file mode 100644
index 0000000..80e867a
--- /dev/null
+++ b/demo_app/macos/Runner/Base.lproj/MainMenu.xib
@@ -0,0 +1,343 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo_app/macos/Runner/Configs/AppInfo.xcconfig b/demo_app/macos/Runner/Configs/AppInfo.xcconfig
new file mode 100644
index 0000000..49e4561
--- /dev/null
+++ b/demo_app/macos/Runner/Configs/AppInfo.xcconfig
@@ -0,0 +1,14 @@
+// Application-level settings for the Runner target.
+//
+// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
+// future. If not, the values below would default to using the project name when this becomes a
+// 'flutter create' template.
+
+// The application's name. By default this is also the title of the Flutter window.
+PRODUCT_NAME = demo_app
+
+// The application's bundle identifier
+PRODUCT_BUNDLE_IDENTIFIER = com.example.demoApp
+
+// The copyright displayed in application information
+PRODUCT_COPYRIGHT = Copyright ยฉ 2022 com.example. All rights reserved.
diff --git a/demo_app/macos/Runner/Configs/Debug.xcconfig b/demo_app/macos/Runner/Configs/Debug.xcconfig
new file mode 100644
index 0000000..36b0fd9
--- /dev/null
+++ b/demo_app/macos/Runner/Configs/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Debug.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/demo_app/macos/Runner/Configs/Release.xcconfig b/demo_app/macos/Runner/Configs/Release.xcconfig
new file mode 100644
index 0000000..dff4f49
--- /dev/null
+++ b/demo_app/macos/Runner/Configs/Release.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Release.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/demo_app/macos/Runner/Configs/Warnings.xcconfig b/demo_app/macos/Runner/Configs/Warnings.xcconfig
new file mode 100644
index 0000000..42bcbf4
--- /dev/null
+++ b/demo_app/macos/Runner/Configs/Warnings.xcconfig
@@ -0,0 +1,13 @@
+WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
+GCC_WARN_UNDECLARED_SELECTOR = YES
+CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
+CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
+CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
+CLANG_WARN_PRAGMA_PACK = YES
+CLANG_WARN_STRICT_PROTOTYPES = YES
+CLANG_WARN_COMMA = YES
+GCC_WARN_STRICT_SELECTOR_MATCH = YES
+CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
+CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
+GCC_WARN_SHADOW = YES
+CLANG_WARN_UNREACHABLE_CODE = YES
diff --git a/demo_app/macos/Runner/DebugProfile.entitlements b/demo_app/macos/Runner/DebugProfile.entitlements
new file mode 100644
index 0000000..dddb8a3
--- /dev/null
+++ b/demo_app/macos/Runner/DebugProfile.entitlements
@@ -0,0 +1,12 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.network.server
+
+
+
diff --git a/demo_app/macos/Runner/Info.plist b/demo_app/macos/Runner/Info.plist
new file mode 100644
index 0000000..4789daa
--- /dev/null
+++ b/demo_app/macos/Runner/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIconFile
+
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSMinimumSystemVersion
+ $(MACOSX_DEPLOYMENT_TARGET)
+ NSHumanReadableCopyright
+ $(PRODUCT_COPYRIGHT)
+ NSMainNibFile
+ MainMenu
+ NSPrincipalClass
+ NSApplication
+
+
diff --git a/demo_app/macos/Runner/MainFlutterWindow.swift b/demo_app/macos/Runner/MainFlutterWindow.swift
new file mode 100644
index 0000000..2722837
--- /dev/null
+++ b/demo_app/macos/Runner/MainFlutterWindow.swift
@@ -0,0 +1,15 @@
+import Cocoa
+import FlutterMacOS
+
+class MainFlutterWindow: NSWindow {
+ override func awakeFromNib() {
+ let flutterViewController = FlutterViewController.init()
+ let windowFrame = self.frame
+ self.contentViewController = flutterViewController
+ self.setFrame(windowFrame, display: true)
+
+ RegisterGeneratedPlugins(registry: flutterViewController)
+
+ super.awakeFromNib()
+ }
+}
diff --git a/demo_app/macos/Runner/Release.entitlements b/demo_app/macos/Runner/Release.entitlements
new file mode 100644
index 0000000..852fa1a
--- /dev/null
+++ b/demo_app/macos/Runner/Release.entitlements
@@ -0,0 +1,8 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+
+
diff --git a/pubspec.yaml b/demo_app/pubspec.yaml
similarity index 62%
rename from pubspec.yaml
rename to demo_app/pubspec.yaml
index 23c5b40..0a4d0fd 100644
--- a/pubspec.yaml
+++ b/demo_app/pubspec.yaml
@@ -1,6 +1,10 @@
-name: learn_flutter
+name: demo_app
description: A new Flutter project.
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
@@ -8,31 +12,49 @@ description: A new Flutter project.
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
-# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
+# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+# In Windows, build-name is used as the major, minor, and patch parts
+# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
- sdk: ">=2.1.0 <3.0.0"
+ sdk: '>=2.18.2 <3.0.0'
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
+
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: ^0.1.2
+ cupertino_icons: ^1.0.2
+ http: ^0.13.5
dev_dependencies:
flutter_test:
sdk: flutter
+ # The "flutter_lints" package below contains a set of recommended lints to
+ # encourage good coding practices. The lint set provided by the package is
+ # activated in the `analysis_options.yaml` file located at the root of your
+ # package. See that file for information about deactivating specific lint
+ # rules and activating additional ones.
+ flutter_lints: ^2.0.0
+ mockito: 5.3.2
+ build_runner: 2.3.2
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
-# The following section is specific to Flutter.
+# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
@@ -46,7 +68,7 @@ flutter:
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
- # https://flutter.dev/assets-and-images/#resolution-aware.
+ # https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
diff --git a/demo_app/test/unit/todoRepository_test.dart b/demo_app/test/unit/todoRepository_test.dart
new file mode 100644
index 0000000..0d7d3f6
--- /dev/null
+++ b/demo_app/test/unit/todoRepository_test.dart
@@ -0,0 +1,44 @@
+import 'package:demo_app/models/todo.dart';
+import 'package:demo_app/repository/todoRepository.dart';
+import 'package:http/http.dart' as http;
+import 'package:mockito/annotations.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+import 'todoRepository_test.mocks.dart';
+
+// Generate a MockClient using the Mockito package.
+// Create new instances of this class in each test.
+@GenerateMocks([http.Client])
+void main() {
+ test('Checks if a Todo array is yielded and has the expected length',
+ () async {
+ final client = MockClient();
+ final repo = HTTPTodoRepository();
+
+ // Use Mockito to return a successful response when it calls the
+ // provided http.Client.
+ when(client.get(Uri.parse('https://jsonplaceholder.typicode.com/todos')))
+ .thenAnswer((_) async => http.Response(
+ '[{"userId": 1, "id": 2, "title": "mock", "completed": true}]',
+ 200));
+
+ repo.client = client;
+
+ expect(await repo.getTodos().then((value) => value.length), equals(1));
+ });
+
+ test('throws an exception if the http call completes with an error', () {
+ final client = MockClient();
+ final repo = HTTPTodoRepository();
+
+ // Use Mockito to return an unsuccessful response when it calls the
+ // provided http.Client.
+ when(client.get(Uri.parse('https://jsonplaceholder.typicode.com/todos')))
+ .thenAnswer((_) async => http.Response('Not Found', 404));
+
+ repo.client = client;
+
+ expect(repo.getTodos(), throwsException);
+ });
+}
diff --git a/demo_app/test/unit/todoRepository_test.mocks.dart b/demo_app/test/unit/todoRepository_test.mocks.dart
new file mode 100644
index 0000000..2fe405d
--- /dev/null
+++ b/demo_app/test/unit/todoRepository_test.mocks.dart
@@ -0,0 +1,263 @@
+// Mocks generated by Mockito 5.3.2 from annotations
+// in demo_app/test/unit/todoRepository_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i3;
+import 'dart:convert' as _i4;
+import 'dart:typed_data' as _i5;
+
+import 'package:http/http.dart' as _i2;
+import 'package:mockito/mockito.dart' as _i1;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakeResponse_0 extends _i1.SmartFake implements _i2.Response {
+ _FakeResponse_0(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeStreamedResponse_1 extends _i1.SmartFake
+ implements _i2.StreamedResponse {
+ _FakeStreamedResponse_1(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+/// A class which mocks [Client].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockClient extends _i1.Mock implements _i2.Client {
+ MockClient() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i3.Future<_i2.Response> head(
+ Uri? url, {
+ Map? headers,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #head,
+ [url],
+ {#headers: headers},
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0(
+ this,
+ Invocation.method(
+ #head,
+ [url],
+ {#headers: headers},
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+ @override
+ _i3.Future<_i2.Response> get(
+ Uri? url, {
+ Map? headers,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #get,
+ [url],
+ {#headers: headers},
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0(
+ this,
+ Invocation.method(
+ #get,
+ [url],
+ {#headers: headers},
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+ @override
+ _i3.Future<_i2.Response> post(
+ Uri? url, {
+ Map? headers,
+ Object? body,
+ _i4.Encoding? encoding,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #post,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0(
+ this,
+ Invocation.method(
+ #post,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+ @override
+ _i3.Future<_i2.Response> put(
+ Uri? url, {
+ Map? headers,
+ Object? body,
+ _i4.Encoding? encoding,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #put,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0(
+ this,
+ Invocation.method(
+ #put,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+ @override
+ _i3.Future<_i2.Response> patch(
+ Uri? url, {
+ Map? headers,
+ Object? body,
+ _i4.Encoding? encoding,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #patch,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0(
+ this,
+ Invocation.method(
+ #patch,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+ @override
+ _i3.Future<_i2.Response> delete(
+ Uri? url, {
+ Map? headers,
+ Object? body,
+ _i4.Encoding? encoding,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #delete,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0(
+ this,
+ Invocation.method(
+ #delete,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+ @override
+ _i3.Future read(
+ Uri? url, {
+ Map? headers,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #read,
+ [url],
+ {#headers: headers},
+ ),
+ returnValue: _i3.Future.value(''),
+ ) as _i3.Future);
+ @override
+ _i3.Future<_i5.Uint8List> readBytes(
+ Uri? url, {
+ Map? headers,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #readBytes,
+ [url],
+ {#headers: headers},
+ ),
+ returnValue: _i3.Future<_i5.Uint8List>.value(_i5.Uint8List(0)),
+ ) as _i3.Future<_i5.Uint8List>);
+ @override
+ _i3.Future<_i2.StreamedResponse> send(_i2.BaseRequest? request) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #send,
+ [request],
+ ),
+ returnValue:
+ _i3.Future<_i2.StreamedResponse>.value(_FakeStreamedResponse_1(
+ this,
+ Invocation.method(
+ #send,
+ [request],
+ ),
+ )),
+ ) as _i3.Future<_i2.StreamedResponse>);
+ @override
+ void close() => super.noSuchMethod(
+ Invocation.method(
+ #close,
+ [],
+ ),
+ returnValueForMissingStub: null,
+ );
+}
diff --git a/demo_app/test/unit/todoService_test.dart b/demo_app/test/unit/todoService_test.dart
new file mode 100644
index 0000000..8b0d99d
--- /dev/null
+++ b/demo_app/test/unit/todoService_test.dart
@@ -0,0 +1,51 @@
+import 'package:demo_app/repository/todoRepository.dart';
+import 'package:demo_app/services/todoService.dart';
+import 'package:http/http.dart' as http;
+import 'package:mockito/annotations.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+
+import 'todoService_test.mocks.dart';
+
+// Generate a MockClient using the Mockito package.
+// Create new instances of this class in each test.
+@GenerateMocks([http.Client])
+void main() {
+ test(
+ 'Checks if a Todo array is returned from the service and has the expected length',
+ () async {
+ final client = MockClient();
+ final repo = HTTPTodoRepository();
+
+ // Use Mockito to return a successful response when it calls the
+ // provided http.Client.
+ when(client.get(Uri.parse('https://jsonplaceholder.typicode.com/todos')))
+ .thenAnswer((_) async => http.Response(
+ '[{"userId": 1, "id": 2, "title": "mock", "completed": true}]',
+ 200));
+
+ repo.client = client;
+
+ final service = TodoService();
+ service.todoRepository = repo;
+
+ expect(await service.getTodos().then((value) => value.length), equals(1));
+ });
+
+ test('throws an exception if the http call completes with an error', () {
+ final client = MockClient();
+ final repo = HTTPTodoRepository();
+
+ // Use Mockito to return an unsuccessful response when it calls the
+ // provided http.Client.
+ when(client.get(Uri.parse('https://jsonplaceholder.typicode.com/todos')))
+ .thenAnswer((_) async => http.Response('Not Found', 404));
+
+ repo.client = client;
+
+ final service = TodoService();
+ service.todoRepository = repo;
+
+ expect(service.getTodos(), throwsException);
+ });
+}
diff --git a/demo_app/test/unit/todoService_test.mocks.dart b/demo_app/test/unit/todoService_test.mocks.dart
new file mode 100644
index 0000000..2e72242
--- /dev/null
+++ b/demo_app/test/unit/todoService_test.mocks.dart
@@ -0,0 +1,263 @@
+// Mocks generated by Mockito 5.3.2 from annotations
+// in demo_app/test/unit/todoService_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i3;
+import 'dart:convert' as _i4;
+import 'dart:typed_data' as _i5;
+
+import 'package:http/http.dart' as _i2;
+import 'package:mockito/mockito.dart' as _i1;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakeResponse_0 extends _i1.SmartFake implements _i2.Response {
+ _FakeResponse_0(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+class _FakeStreamedResponse_1 extends _i1.SmartFake
+ implements _i2.StreamedResponse {
+ _FakeStreamedResponse_1(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+/// A class which mocks [Client].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockClient extends _i1.Mock implements _i2.Client {
+ MockClient() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i3.Future<_i2.Response> head(
+ Uri? url, {
+ Map? headers,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #head,
+ [url],
+ {#headers: headers},
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0(
+ this,
+ Invocation.method(
+ #head,
+ [url],
+ {#headers: headers},
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+ @override
+ _i3.Future<_i2.Response> get(
+ Uri? url, {
+ Map? headers,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #get,
+ [url],
+ {#headers: headers},
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0(
+ this,
+ Invocation.method(
+ #get,
+ [url],
+ {#headers: headers},
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+ @override
+ _i3.Future<_i2.Response> post(
+ Uri? url, {
+ Map? headers,
+ Object? body,
+ _i4.Encoding? encoding,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #post,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0(
+ this,
+ Invocation.method(
+ #post,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+ @override
+ _i3.Future<_i2.Response> put(
+ Uri? url, {
+ Map? headers,
+ Object? body,
+ _i4.Encoding? encoding,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #put,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0(
+ this,
+ Invocation.method(
+ #put,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+ @override
+ _i3.Future<_i2.Response> patch(
+ Uri? url, {
+ Map? headers,
+ Object? body,
+ _i4.Encoding? encoding,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #patch,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0(
+ this,
+ Invocation.method(
+ #patch,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+ @override
+ _i3.Future<_i2.Response> delete(
+ Uri? url, {
+ Map? headers,
+ Object? body,
+ _i4.Encoding? encoding,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #delete,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ returnValue: _i3.Future<_i2.Response>.value(_FakeResponse_0(
+ this,
+ Invocation.method(
+ #delete,
+ [url],
+ {
+ #headers: headers,
+ #body: body,
+ #encoding: encoding,
+ },
+ ),
+ )),
+ ) as _i3.Future<_i2.Response>);
+ @override
+ _i3.Future read(
+ Uri? url, {
+ Map? headers,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #read,
+ [url],
+ {#headers: headers},
+ ),
+ returnValue: _i3.Future.value(''),
+ ) as _i3.Future);
+ @override
+ _i3.Future<_i5.Uint8List> readBytes(
+ Uri? url, {
+ Map? headers,
+ }) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #readBytes,
+ [url],
+ {#headers: headers},
+ ),
+ returnValue: _i3.Future<_i5.Uint8List>.value(_i5.Uint8List(0)),
+ ) as _i3.Future<_i5.Uint8List>);
+ @override
+ _i3.Future<_i2.StreamedResponse> send(_i2.BaseRequest? request) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #send,
+ [request],
+ ),
+ returnValue:
+ _i3.Future<_i2.StreamedResponse>.value(_FakeStreamedResponse_1(
+ this,
+ Invocation.method(
+ #send,
+ [request],
+ ),
+ )),
+ ) as _i3.Future<_i2.StreamedResponse>);
+ @override
+ void close() => super.noSuchMethod(
+ Invocation.method(
+ #close,
+ [],
+ ),
+ returnValueForMissingStub: null,
+ );
+}
diff --git a/demo_app/test/widget/widget_test.dart b/demo_app/test/widget/widget_test.dart
new file mode 100644
index 0000000..6fc0e59
--- /dev/null
+++ b/demo_app/test/widget/widget_test.dart
@@ -0,0 +1,163 @@
+import 'package:demo_app/models/todo.dart';
+import 'package:demo_app/services/todoService.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/mockito.dart';
+import 'package:mockito/annotations.dart';
+
+import 'package:demo_app/main.dart';
+
+import 'widget_test.mocks.dart';
+
+@GenerateMocks([TodoService])
+void main() {
+ testWidgets('Check if appbar renders', (WidgetTester tester) async {
+ // Build our app and trigger a frame.
+ await tester.pumpWidget(const MyApp());
+
+ // Verify that the appbar renders
+ expect(find.text('todo item list'), findsOneWidget);
+ });
+
+ testWidgets('Check if item list is rendered', (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos()).thenAnswer((_) async =>
+ [const Todo(userId: 1, id: 1, title: 'mocktitle', completed: true)]);
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Expect the mocked todo item to be displayed
+ expect(find.text('mocktitle'), findsOneWidget);
+ });
+
+ testWidgets('Error should be displayed if the server returns error',
+ (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos())
+ .thenAnswer((_) async => throw Exception('Error getting todos.'));
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Expect the mocked todo item to be displayed
+ expect(find.text('Exception: Error getting todos.'), findsOneWidget);
+ });
+
+ testWidgets('Tapping on a todo item and navigating to the done list page.',
+ (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos()).thenAnswer((_) async => [
+ const Todo(userId: 1, id: 1, title: 'mocktitle', completed: true),
+ const Todo(userId: 1, id: 2, title: 'mocktitle2', completed: true),
+ ]);
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Tapping on a todo
+ await tester.tap(find.text('mocktitle'));
+ await tester.pumpAndSettle();
+
+ // Navigating away
+ await tester.tap(find.byIcon((Icons.list)));
+ await tester.pumpAndSettle();
+
+ // Expect the todo list page to be shown
+ expect(find.text('completed todo list'), findsOneWidget);
+ expect(find.text('todo item list'), findsNothing);
+ });
+
+ testWidgets('Navigating to the todo list directly and find empty widget array',
+ (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos()).thenAnswer((_) async => [
+ const Todo(userId: 1, id: 1, title: 'mocktitle', completed: true),
+ const Todo(userId: 1, id: 2, title: 'mocktitle2', completed: true),
+ ]);
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Navigating away
+ await tester.tap(find.byIcon((Icons.list)));
+ await tester.pumpAndSettle();
+
+ // Expect the todo list page to be shown
+ expect(find.text('completed todo list'), findsOneWidget);
+ });
+
+ testWidgets('Marking todo as done and then as undone',
+ (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos()).thenAnswer((_) async => [
+ const Todo(userId: 1, id: 1, title: 'mocktitle', completed: true),
+ const Todo(userId: 1, id: 2, title: 'mocktitle2', completed: true),
+ ]);
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Tap and untap
+ await tester.tap(find.text('mocktitle'));
+ await tester.pumpAndSettle();
+
+ await tester.tap(find.text('mocktitle'));
+ await tester.pumpAndSettle();
+
+ // Expect the todo list page to be shown
+ expect(find.text('mocktitle'), findsOneWidget);
+ });
+
+ testWidgets('Testing main mount',
+ (WidgetTester tester) async {
+ final TodoService mockService = MockTodoService();
+
+ when(mockService.getTodos()).thenAnswer((_) async => [
+ const Todo(userId: 1, id: 1, title: 'mocktitle', completed: true),
+ const Todo(userId: 1, id: 2, title: 'mocktitle2', completed: true),
+ ]);
+
+ Widget testWidget = MediaQuery(
+ data: const MediaQueryData(),
+ child: MaterialApp(home: TodoList(todoService: mockService)));
+
+ await tester.pumpWidget(testWidget);
+ await tester.pump(const Duration(milliseconds: 100));
+
+ // Tap and untap
+ await tester.tap(find.text('mocktitle'));
+ await tester.pumpAndSettle();
+
+ await tester.tap(find.text('mocktitle'));
+ await tester.pumpAndSettle();
+
+ // Expect the todo list page to be shown
+ expect(find.text('mocktitle'), findsOneWidget);
+ });
+}
diff --git a/demo_app/test/widget/widget_test.mocks.dart b/demo_app/test/widget/widget_test.mocks.dart
new file mode 100644
index 0000000..aed4c89
--- /dev/null
+++ b/demo_app/test/widget/widget_test.mocks.dart
@@ -0,0 +1,67 @@
+// Mocks generated by Mockito 5.3.2 from annotations
+// in demo_app/test/widget_test.dart.
+// Do not manually edit this file.
+
+// ignore_for_file: no_leading_underscores_for_library_prefixes
+import 'dart:async' as _i4;
+
+import 'package:demo_app/models/todo.dart' as _i5;
+import 'package:demo_app/repository/todoRepository.dart' as _i2;
+import 'package:demo_app/services/todoService.dart' as _i3;
+import 'package:mockito/mockito.dart' as _i1;
+
+// ignore_for_file: type=lint
+// ignore_for_file: avoid_redundant_argument_values
+// ignore_for_file: avoid_setters_without_getters
+// ignore_for_file: comment_references
+// ignore_for_file: implementation_imports
+// ignore_for_file: invalid_use_of_visible_for_testing_member
+// ignore_for_file: prefer_const_constructors
+// ignore_for_file: unnecessary_parenthesis
+// ignore_for_file: camel_case_types
+// ignore_for_file: subtype_of_sealed_class
+
+class _FakeTodoRepository_0 extends _i1.SmartFake
+ implements _i2.TodoRepository {
+ _FakeTodoRepository_0(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
+/// A class which mocks [TodoService].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockTodoService extends _i1.Mock implements _i3.TodoService {
+ MockTodoService() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i2.TodoRepository get todoRepository => (super.noSuchMethod(
+ Invocation.getter(#todoRepository),
+ returnValue: _FakeTodoRepository_0(
+ this,
+ Invocation.getter(#todoRepository),
+ ),
+ ) as _i2.TodoRepository);
+ @override
+ set todoRepository(_i2.TodoRepository? _todoRepository) => super.noSuchMethod(
+ Invocation.setter(
+ #todoRepository,
+ _todoRepository,
+ ),
+ returnValueForMissingStub: null,
+ );
+ @override
+ _i4.Future> getTodos() => (super.noSuchMethod(
+ Invocation.method(
+ #getTodos,
+ [],
+ ),
+ returnValue: _i4.Future>.value(<_i5.Todo>[]),
+ ) as _i4.Future>);
+}
diff --git a/demo_app/web/favicon.png b/demo_app/web/favicon.png
new file mode 100644
index 0000000..8aaa46a
Binary files /dev/null and b/demo_app/web/favicon.png differ
diff --git a/web/icons/Icon-192.png b/demo_app/web/icons/Icon-192.png
similarity index 100%
rename from web/icons/Icon-192.png
rename to demo_app/web/icons/Icon-192.png
diff --git a/web/icons/Icon-512.png b/demo_app/web/icons/Icon-512.png
similarity index 100%
rename from web/icons/Icon-512.png
rename to demo_app/web/icons/Icon-512.png
diff --git a/demo_app/web/icons/Icon-maskable-192.png b/demo_app/web/icons/Icon-maskable-192.png
new file mode 100644
index 0000000..eb9b4d7
Binary files /dev/null and b/demo_app/web/icons/Icon-maskable-192.png differ
diff --git a/demo_app/web/icons/Icon-maskable-512.png b/demo_app/web/icons/Icon-maskable-512.png
new file mode 100644
index 0000000..d69c566
Binary files /dev/null and b/demo_app/web/icons/Icon-maskable-512.png differ
diff --git a/demo_app/web/index.html b/demo_app/web/index.html
new file mode 100644
index 0000000..3c31370
--- /dev/null
+++ b/demo_app/web/index.html
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ demo_app
+
+
+
+
+
+
+
+
+
+
diff --git a/web/manifest.json b/demo_app/web/manifest.json
similarity index 53%
rename from web/manifest.json
rename to demo_app/web/manifest.json
index 9c1ca76..5f80e50 100644
--- a/web/manifest.json
+++ b/demo_app/web/manifest.json
@@ -1,8 +1,8 @@
{
- "name": "learn_flutter",
- "short_name": "learn_flutter",
+ "name": "demo_app",
+ "short_name": "demo_app",
"start_url": ".",
- "display": "minimal-ui",
+ "display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
@@ -18,6 +18,18 @@
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
+ },
+ {
+ "src": "icons/Icon-maskable-192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "maskable"
+ },
+ {
+ "src": "icons/Icon-maskable-512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "maskable"
}
]
}
diff --git a/demo_app/windows/.gitignore b/demo_app/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/demo_app/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/demo_app/windows/CMakeLists.txt b/demo_app/windows/CMakeLists.txt
new file mode 100644
index 0000000..01b6591
--- /dev/null
+++ b/demo_app/windows/CMakeLists.txt
@@ -0,0 +1,101 @@
+# Project-level configuration.
+cmake_minimum_required(VERSION 3.14)
+project(demo_app LANGUAGES CXX)
+
+# The name of the executable created for the application. Change this to change
+# the on-disk name of your application.
+set(BINARY_NAME "demo_app")
+
+# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
+# versions of CMake.
+cmake_policy(SET CMP0063 NEW)
+
+# Define build configuration option.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+ set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+ CACHE STRING "" FORCE)
+else()
+ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE
+ STRING "Flutter build mode" FORCE)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+ "Debug" "Profile" "Release")
+ endif()
+endif()
+# Define settings for the Profile build mode.
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+#
+# Be cautious about adding new options here, as plugins use this function by
+# default. In most cases, you should add new options to specific targets instead
+# of modifying this function.
+function(APPLY_STANDARD_SETTINGS TARGET)
+ target_compile_features(${TARGET} PUBLIC cxx_std_17)
+ target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+ target_compile_options(${TARGET} PRIVATE /EHsc)
+ target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+ target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>")
+endfunction()
+
+# Flutter library and tool build rules.
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build; see runner/CMakeLists.txt.
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+ install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+ DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+ file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+ " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+ DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+ CONFIGURATIONS Profile;Release
+ COMPONENT Runtime)
diff --git a/demo_app/windows/flutter/CMakeLists.txt b/demo_app/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..930d207
--- /dev/null
+++ b/demo_app/windows/flutter/CMakeLists.txt
@@ -0,0 +1,104 @@
+# This file controls Flutter-level build steps. It should not be edited.
+cmake_minimum_required(VERSION 3.14)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+ "flutter_export.h"
+ "flutter_windows.h"
+ "flutter_messenger.h"
+ "flutter_plugin_registrar.h"
+ "flutter_texture_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+ "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+ "core_implementations.cc"
+ "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+ "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+ "flutter_engine.cc"
+ "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+ ${CPP_WRAPPER_SOURCES_CORE}
+ ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+ POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+ CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+ "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+ ${CPP_WRAPPER_SOURCES_CORE}
+ ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+ "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+ OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+ ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+ ${CPP_WRAPPER_SOURCES_APP}
+ ${PHONY_OUTPUT}
+ COMMAND ${CMAKE_COMMAND} -E env
+ ${FLUTTER_TOOL_ENVIRONMENT}
+ "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+ windows-x64 $
+ VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+ "${FLUTTER_LIBRARY}"
+ ${FLUTTER_LIBRARY_HEADERS}
+ ${CPP_WRAPPER_SOURCES_CORE}
+ ${CPP_WRAPPER_SOURCES_PLUGIN}
+ ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/demo_app/windows/flutter/generated_plugin_registrant.cc b/demo_app/windows/flutter/generated_plugin_registrant.cc
new file mode 100644
index 0000000..8b6d468
--- /dev/null
+++ b/demo_app/windows/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,11 @@
+//
+// Generated file. Do not edit.
+//
+
+// clang-format off
+
+#include "generated_plugin_registrant.h"
+
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {
+}
diff --git a/demo_app/windows/flutter/generated_plugin_registrant.h b/demo_app/windows/flutter/generated_plugin_registrant.h
new file mode 100644
index 0000000..dc139d8
--- /dev/null
+++ b/demo_app/windows/flutter/generated_plugin_registrant.h
@@ -0,0 +1,15 @@
+//
+// Generated file. Do not edit.
+//
+
+// clang-format off
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif // GENERATED_PLUGIN_REGISTRANT_
diff --git a/demo_app/windows/flutter/generated_plugins.cmake b/demo_app/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..b93c4c3
--- /dev/null
+++ b/demo_app/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,23 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+ target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/demo_app/windows/runner/CMakeLists.txt b/demo_app/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..17411a8
--- /dev/null
+++ b/demo_app/windows/runner/CMakeLists.txt
@@ -0,0 +1,39 @@
+cmake_minimum_required(VERSION 3.14)
+project(runner LANGUAGES CXX)
+
+# Define the application target. To change its name, change BINARY_NAME in the
+# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
+# work.
+#
+# Any new source files that you add to the application should be added here.
+add_executable(${BINARY_NAME} WIN32
+ "flutter_window.cpp"
+ "main.cpp"
+ "utils.cpp"
+ "win32_window.cpp"
+ "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+ "Runner.rc"
+ "runner.exe.manifest"
+)
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
+apply_standard_settings(${BINARY_NAME})
+
+# Add preprocessor definitions for the build version.
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
+
+# Disable Windows macros that collide with C++ standard library functions.
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+
+# Add dependency libraries and include directories. Add any application-specific
+# dependencies here.
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+
+# Run the Flutter tool portions of the build. This must not be removed.
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/demo_app/windows/runner/Runner.rc b/demo_app/windows/runner/Runner.rc
new file mode 100644
index 0000000..f3ead6c
--- /dev/null
+++ b/demo_app/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON ICON "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
+#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
+#else
+#define VERSION_AS_NUMBER 1,0,0,0
+#endif
+
+#if defined(FLUTTER_VERSION)
+#define VERSION_AS_STRING FLUTTER_VERSION
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "com.example" "\0"
+ VALUE "FileDescription", "demo_app" "\0"
+ VALUE "FileVersion", VERSION_AS_STRING "\0"
+ VALUE "InternalName", "demo_app" "\0"
+ VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0"
+ VALUE "OriginalFilename", "demo_app.exe" "\0"
+ VALUE "ProductName", "demo_app" "\0"
+ VALUE "ProductVersion", VERSION_AS_STRING "\0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
diff --git a/demo_app/windows/runner/flutter_window.cpp b/demo_app/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..b43b909
--- /dev/null
+++ b/demo_app/windows/runner/flutter_window.cpp
@@ -0,0 +1,61 @@
+#include "flutter_window.h"
+
+#include
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(const flutter::DartProject& project)
+ : project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+ if (!Win32Window::OnCreate()) {
+ return false;
+ }
+
+ RECT frame = GetClientArea();
+
+ // The size here must match the window dimensions to avoid unnecessary surface
+ // creation / destruction in the startup path.
+ flutter_controller_ = std::make_unique(
+ frame.right - frame.left, frame.bottom - frame.top, project_);
+ // Ensure that basic setup of the controller was successful.
+ if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+ return false;
+ }
+ RegisterPlugins(flutter_controller_->engine());
+ SetChildContent(flutter_controller_->view()->GetNativeWindow());
+ return true;
+}
+
+void FlutterWindow::OnDestroy() {
+ if (flutter_controller_) {
+ flutter_controller_ = nullptr;
+ }
+
+ Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept {
+ // Give Flutter, including plugins, an opportunity to handle window messages.
+ if (flutter_controller_) {
+ std::optional result =
+ flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+ lparam);
+ if (result) {
+ return *result;
+ }
+ }
+
+ switch (message) {
+ case WM_FONTCHANGE:
+ flutter_controller_->engine()->ReloadSystemFonts();
+ break;
+ }
+
+ return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/demo_app/windows/runner/flutter_window.h b/demo_app/windows/runner/flutter_window.h
new file mode 100644
index 0000000..6da0652
--- /dev/null
+++ b/demo_app/windows/runner/flutter_window.h
@@ -0,0 +1,33 @@
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include
+#include
+
+#include
+
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+ // Creates a new FlutterWindow hosting a Flutter view running |project|.
+ explicit FlutterWindow(const flutter::DartProject& project);
+ virtual ~FlutterWindow();
+
+ protected:
+ // Win32Window:
+ bool OnCreate() override;
+ void OnDestroy() override;
+ LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+ LPARAM const lparam) noexcept override;
+
+ private:
+ // The project to run.
+ flutter::DartProject project_;
+
+ // The Flutter instance hosted by this window.
+ std::unique_ptr flutter_controller_;
+};
+
+#endif // RUNNER_FLUTTER_WINDOW_H_
diff --git a/demo_app/windows/runner/main.cpp b/demo_app/windows/runner/main.cpp
new file mode 100644
index 0000000..817450c
--- /dev/null
+++ b/demo_app/windows/runner/main.cpp
@@ -0,0 +1,43 @@
+#include
+#include
+#include
+
+#include "flutter_window.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+ _In_ wchar_t *command_line, _In_ int show_command) {
+ // Attach to console when present (e.g., 'flutter run') or create a
+ // new console when running with a debugger.
+ if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+ CreateAndAttachConsole();
+ }
+
+ // Initialize COM, so that it is available for use in the library and/or
+ // plugins.
+ ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+ flutter::DartProject project(L"data");
+
+ std::vector command_line_arguments =
+ GetCommandLineArguments();
+
+ project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+ FlutterWindow window(project);
+ Win32Window::Point origin(10, 10);
+ Win32Window::Size size(1280, 720);
+ if (!window.CreateAndShow(L"demo_app", origin, size)) {
+ return EXIT_FAILURE;
+ }
+ window.SetQuitOnClose(true);
+
+ ::MSG msg;
+ while (::GetMessage(&msg, nullptr, 0, 0)) {
+ ::TranslateMessage(&msg);
+ ::DispatchMessage(&msg);
+ }
+
+ ::CoUninitialize();
+ return EXIT_SUCCESS;
+}
diff --git a/demo_app/windows/runner/resource.h b/demo_app/windows/runner/resource.h
new file mode 100644
index 0000000..66a65d1
--- /dev/null
+++ b/demo_app/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/demo_app/windows/runner/resources/app_icon.ico b/demo_app/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
Binary files /dev/null and b/demo_app/windows/runner/resources/app_icon.ico differ
diff --git a/demo_app/windows/runner/runner.exe.manifest b/demo_app/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..a42ea76
--- /dev/null
+++ b/demo_app/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+
+
+
+
+ PerMonitorV2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo_app/windows/runner/utils.cpp b/demo_app/windows/runner/utils.cpp
new file mode 100644
index 0000000..f5bf9fa
--- /dev/null
+++ b/demo_app/windows/runner/utils.cpp
@@ -0,0 +1,64 @@
+#include "utils.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+void CreateAndAttachConsole() {
+ if (::AllocConsole()) {
+ FILE *unused;
+ if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+ _dup2(_fileno(stdout), 1);
+ }
+ if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+ _dup2(_fileno(stdout), 2);
+ }
+ std::ios::sync_with_stdio();
+ FlutterDesktopResyncOutputStreams();
+ }
+}
+
+std::vector GetCommandLineArguments() {
+ // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
+ int argc;
+ wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
+ if (argv == nullptr) {
+ return std::vector();
+ }
+
+ std::vector command_line_arguments;
+
+ // Skip the first argument as it's the binary name.
+ for (int i = 1; i < argc; i++) {
+ command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
+ }
+
+ ::LocalFree(argv);
+
+ return command_line_arguments;
+}
+
+std::string Utf8FromUtf16(const wchar_t* utf16_string) {
+ if (utf16_string == nullptr) {
+ return std::string();
+ }
+ int target_length = ::WideCharToMultiByte(
+ CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+ -1, nullptr, 0, nullptr, nullptr);
+ std::string utf8_string;
+ if (target_length == 0 || target_length > utf8_string.max_size()) {
+ return utf8_string;
+ }
+ utf8_string.resize(target_length);
+ int converted_length = ::WideCharToMultiByte(
+ CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
+ -1, utf8_string.data(),
+ target_length, nullptr, nullptr);
+ if (converted_length == 0) {
+ return std::string();
+ }
+ return utf8_string;
+}
diff --git a/demo_app/windows/runner/utils.h b/demo_app/windows/runner/utils.h
new file mode 100644
index 0000000..3879d54
--- /dev/null
+++ b/demo_app/windows/runner/utils.h
@@ -0,0 +1,19 @@
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+#include
+#include
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
+// encoded in UTF-8. Returns an empty std::string on failure.
+std::string Utf8FromUtf16(const wchar_t* utf16_string);
+
+// Gets the command line arguments passed in as a std::vector,
+// encoded in UTF-8. Returns an empty std::vector on failure.
+std::vector GetCommandLineArguments();
+
+#endif // RUNNER_UTILS_H_
diff --git a/demo_app/windows/runner/win32_window.cpp b/demo_app/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..c10f08d
--- /dev/null
+++ b/demo_app/windows/runner/win32_window.cpp
@@ -0,0 +1,245 @@
+#include "win32_window.h"
+
+#include
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+ return static_cast(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+ HMODULE user32_module = LoadLibraryA("User32.dll");
+ if (!user32_module) {
+ return;
+ }
+ auto enable_non_client_dpi_scaling =
+ reinterpret_cast(
+ GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+ if (enable_non_client_dpi_scaling != nullptr) {
+ enable_non_client_dpi_scaling(hwnd);
+ FreeLibrary(user32_module);
+ }
+}
+
+} // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+ ~WindowClassRegistrar() = default;
+
+ // Returns the singleton registar instance.
+ static WindowClassRegistrar* GetInstance() {
+ if (!instance_) {
+ instance_ = new WindowClassRegistrar();
+ }
+ return instance_;
+ }
+
+ // Returns the name of the window class, registering the class if it hasn't
+ // previously been registered.
+ const wchar_t* GetWindowClass();
+
+ // Unregisters the window class. Should only be called if there are no
+ // instances of the window.
+ void UnregisterWindowClass();
+
+ private:
+ WindowClassRegistrar() = default;
+
+ static WindowClassRegistrar* instance_;
+
+ bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+ if (!class_registered_) {
+ WNDCLASS window_class{};
+ window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+ window_class.lpszClassName = kWindowClassName;
+ window_class.style = CS_HREDRAW | CS_VREDRAW;
+ window_class.cbClsExtra = 0;
+ window_class.cbWndExtra = 0;
+ window_class.hInstance = GetModuleHandle(nullptr);
+ window_class.hIcon =
+ LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+ window_class.hbrBackground = 0;
+ window_class.lpszMenuName = nullptr;
+ window_class.lpfnWndProc = Win32Window::WndProc;
+ RegisterClass(&window_class);
+ class_registered_ = true;
+ }
+ return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+ UnregisterClass(kWindowClassName, nullptr);
+ class_registered_ = false;
+}
+
+Win32Window::Win32Window() {
+ ++g_active_window_count;
+}
+
+Win32Window::~Win32Window() {
+ --g_active_window_count;
+ Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title,
+ const Point& origin,
+ const Size& size) {
+ Destroy();
+
+ const wchar_t* window_class =
+ WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+ const POINT target_point = {static_cast(origin.x),
+ static_cast(origin.y)};
+ HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+ UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+ double scale_factor = dpi / 96.0;
+
+ HWND window = CreateWindow(
+ window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+ Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+ Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+ nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+ if (!window) {
+ return false;
+ }
+
+ return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept {
+ if (message == WM_NCCREATE) {
+ auto window_struct = reinterpret_cast(lparam);
+ SetWindowLongPtr(window, GWLP_USERDATA,
+ reinterpret_cast(window_struct->lpCreateParams));
+
+ auto that = static_cast(window_struct->lpCreateParams);
+ EnableFullDpiSupportIfAvailable(window);
+ that->window_handle_ = window;
+ } else if (Win32Window* that = GetThisFromHandle(window)) {
+ return that->MessageHandler(window, message, wparam, lparam);
+ }
+
+ return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept {
+ switch (message) {
+ case WM_DESTROY:
+ window_handle_ = nullptr;
+ Destroy();
+ if (quit_on_close_) {
+ PostQuitMessage(0);
+ }
+ return 0;
+
+ case WM_DPICHANGED: {
+ auto newRectSize = reinterpret_cast(lparam);
+ LONG newWidth = newRectSize->right - newRectSize->left;
+ LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+ SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+ newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+ return 0;
+ }
+ case WM_SIZE: {
+ RECT rect = GetClientArea();
+ if (child_content_ != nullptr) {
+ // Size and position the child window.
+ MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+ rect.bottom - rect.top, TRUE);
+ }
+ return 0;
+ }
+
+ case WM_ACTIVATE:
+ if (child_content_ != nullptr) {
+ SetFocus(child_content_);
+ }
+ return 0;
+ }
+
+ return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+ OnDestroy();
+
+ if (window_handle_) {
+ DestroyWindow(window_handle_);
+ window_handle_ = nullptr;
+ }
+ if (g_active_window_count == 0) {
+ WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+ }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+ return reinterpret_cast(
+ GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+ child_content_ = content;
+ SetParent(content, window_handle_);
+ RECT frame = GetClientArea();
+
+ MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+ frame.bottom - frame.top, true);
+
+ SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+ RECT frame;
+ GetClientRect(window_handle_, &frame);
+ return frame;
+}
+
+HWND Win32Window::GetHandle() {
+ return window_handle_;
+}
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+ quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+ // No-op; provided for subclasses.
+ return true;
+}
+
+void Win32Window::OnDestroy() {
+ // No-op; provided for subclasses.
+}
diff --git a/demo_app/windows/runner/win32_window.h b/demo_app/windows/runner/win32_window.h
new file mode 100644
index 0000000..17ba431
--- /dev/null
+++ b/demo_app/windows/runner/win32_window.h
@@ -0,0 +1,98 @@
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include
+
+#include
+#include
+#include
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+ struct Point {
+ unsigned int x;
+ unsigned int y;
+ Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+ };
+
+ struct Size {
+ unsigned int width;
+ unsigned int height;
+ Size(unsigned int width, unsigned int height)
+ : width(width), height(height) {}
+ };
+
+ Win32Window();
+ virtual ~Win32Window();
+
+ // Creates and shows a win32 window with |title| and position and size using
+ // |origin| and |size|. New windows are created on the default monitor. Window
+ // sizes are specified to the OS in physical pixels, hence to ensure a
+ // consistent size to will treat the width height passed in to this function
+ // as logical pixels and scale to appropriate for the default monitor. Returns
+ // true if the window was created successfully.
+ bool CreateAndShow(const std::wstring& title,
+ const Point& origin,
+ const Size& size);
+
+ // Release OS resources associated with window.
+ void Destroy();
+
+ // Inserts |content| into the window tree.
+ void SetChildContent(HWND content);
+
+ // Returns the backing Window handle to enable clients to set icon and other
+ // window properties. Returns nullptr if the window has been destroyed.
+ HWND GetHandle();
+
+ // If true, closing this window will quit the application.
+ void SetQuitOnClose(bool quit_on_close);
+
+ // Return a RECT representing the bounds of the current client area.
+ RECT GetClientArea();
+
+ protected:
+ // Processes and route salient window messages for mouse handling,
+ // size change and DPI. Delegates handling of these to member overloads that
+ // inheriting classes can handle.
+ virtual LRESULT MessageHandler(HWND window,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept;
+
+ // Called when CreateAndShow is called, allowing subclass window-related
+ // setup. Subclasses should return false if setup fails.
+ virtual bool OnCreate();
+
+ // Called when Destroy is called.
+ virtual void OnDestroy();
+
+ private:
+ friend class WindowClassRegistrar;
+
+ // OS callback called by message pump. Handles the WM_NCCREATE message which
+ // is passed when the non-client area is being created and enables automatic
+ // non-client DPI scaling so that the non-client area automatically
+ // responsponds to changes in DPI. All other messages are handled by
+ // MessageHandler.
+ static LRESULT CALLBACK WndProc(HWND const window,
+ UINT const message,
+ WPARAM const wparam,
+ LPARAM const lparam) noexcept;
+
+ // Retrieves a class instance pointer for |window|
+ static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+ bool quit_on_close_ = false;
+
+ // window handle for top level window.
+ HWND window_handle_ = nullptr;
+
+ // window handle for hosted content.
+ HWND child_content_ = nullptr;
+};
+
+#endif // RUNNER_WIN32_WINDOW_H_
diff --git a/Flutter_TDD_Architecture_Course_notes.md b/guides/Flutter_TDD_Architecture_Course_notes.md
similarity index 100%
rename from Flutter_TDD_Architecture_Course_notes.md
rename to guides/Flutter_TDD_Architecture_Course_notes.md
diff --git a/guides/login-firebase-tutorial.md b/guides/login-firebase-tutorial.md
new file mode 100644
index 0000000..4ef9076
--- /dev/null
+++ b/guides/login-firebase-tutorial.md
@@ -0,0 +1,125 @@
+
+# Signing in with Google using Flutter
+
+After a lot of research I realized that the only way to log in to Google without using Firebase is through the packages provided by Google.
+ Using these packages we can make the user log in without saving their personal data, except the data we want to show , like email and username.
+
+ ![Flutter](https://i.imgur.com/A4kNbpQ.png)
+
+Let's get started. First of all we have to create a new Flutter Project.
+Choose the Flutter Application option.
+
+ ![Flutter](https://i.imgur.com/b6fVARr.png)
+
+Insert the Name of the Project and the company name.
+After that in the 'main.dart' remove all the code except the main and switch the (MyApp) to (MaterialApp).
+Insert 'Title'.
+
+ ![Flutter](https://i.imgur.com/dLfAgUO.png)
+
+ Create a new class to extend a 'StatefulWidget'.
+ Change return 'Container' to 'Scaffold' so we can use the features provided by the Scaffold Class.
+
+ ![Flutter](https://i.imgur.com/Dfe11We.png)
+
+ Enter in 'pubspec.yaml' , then go to google and search for google sign in package flutter or click this link:
+ https://pub.dev/packages/google_sign_in, go to installing and add that command to 'pubspec dependecies'.
+
+
+ ![Flutter](https://i.imgur.com/TQ18gDR.png)
+
+ ![Flutter](https://i.imgur.com/iUtoy36.png)
+
+ Click on "Packages get" as we made a change to the packages to update.
+
+
+ ![Flutter](https://i.imgur.com/81h384m.png)
+
+ Import the Google package to 'main.dart'.
+
+ ![Flutter](https://i.imgur.com/AKibdLx.png)
+
+ Create the 'GoogleSignIn' object, provide the scope profile and email to that object.
+
+ ![Flutter](https://i.imgur.com/9E5uVXH.png)
+
+ You need to create a SignInAccount object.
+ Name that object 'user' to be used to see if he is signed in or signed out.
+
+ Inside the 'Scaffold' use the 'AppBar' to add a Title.
+
+ ![Flutter](https://i.imgur.com/mbgBIlY.png)
+
+ In the 'body' create a method '_buildbody'.
+ Inside that method you need to add a 'ListTile' to display the information,then use a GoogleUserCircleAvatar to display the profile image.
+
+ ![Flutter](https://i.imgur.com/4HObCG5.png)
+
+ Make the title and subtitle show the 'Name' and 'Email' on the screen.
+ Create a button to be pressed with the text 'Sign out'.
+
+![Flutter](https://i.imgur.com/yUXiw3F.png)
+
+![Flutter](https://i.imgur.com/bLaPMsK.png)
+
+
+
+ In short, if the user is not connected, a message appears saying "Not Signed in". If this user is already connected, his data is displayed and a button appears saying "Sign Out".
+
+
+Now, create both _GetSignIn and _GetSignOut methods.
+Use the method silently/ SignInSilently(); -> Used to Sign In the user without interaction.
+
+![Flutter](https://i.imgur.com/LK7YprE.png)
+
+After the UI is completed we need to register the App in the Firebase.
+Even if we don't need to use Firebase the App has to be registered there.
+
+So go to: https://console.firebase.google.com/u/0/
+In the Firebase console, add a new project and get the same package name from your app.
+
+
+![Flutter](https://i.imgur.com/AGFilMM.png)
+
+You will need to get the SHA-1 so go to 'gradlew' and to the terminal and run the command- 'gradlew signinReport'.
+
+![Flutter](https://i.imgur.com/9JD4DGP.png)
+
+![Flutter](https://i.imgur.com/YnweBh6.png)
+
+Insert the SHA-1 in the Firebase space then, download the file and paste it inside the Android > app.
+
+Follow the steps to add the Firebase SDK / Go to people API:
+
+https://developers.google.com/people/v1/getting-started
+
+Follow those 3 steps.
+
+![Flutter](https://i.imgur.com/ro6RiCJ.png)
+
+After that go to credentials and click the user data option.
+Click on the API and go for Android or IOS.
+Name your app again, put the SHA-1 you got from 'gradlew' and the package name you can find in your 'AndroidManifest.xml'.
+
+
+![Flutter](https://i.imgur.com/wkLI4L0.png)
+
+
+Set up the consent screen, go to support email and select your email then click save.
+Go to OAuth 2.0 Cliente IDs, select the Android option.
+
+On your Manifest.xml add below the package name this line:
+
+
+```ruby
+
+```
+
+Then just edit the button as you like and the Google login is fully functional.
+
+
+
+
+
+
+
diff --git a/webview-tutorial.md b/guides/webview-tutorial.md
similarity index 100%
rename from webview-tutorial.md
rename to guides/webview-tutorial.md
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
deleted file mode 100644
index 2667988..0000000
--- a/ios/Runner/Info.plist
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- learn_flutter
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- $(FLUTTER_BUILD_NAME)
- CFBundleSignature
- ????
- CFBundleVersion
- $(FLUTTER_BUILD_NUMBER)
- LSRequiresIPhoneOS
-
- UILaunchStoryboardName
- LaunchScreen
- UIMainStoryboardFile
- Main
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UIViewControllerBasedStatusBarAppearance
-
-
-
diff --git a/learn_flutter.iml b/learn_flutter.iml
deleted file mode 100644
index e5c8371..0000000
--- a/learn_flutter.iml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
deleted file mode 100644
index 5a7af45..0000000
--- a/lib/main.dart
+++ /dev/null
@@ -1,111 +0,0 @@
-import 'package:flutter/material.dart';
-
-void main() => runApp(MyApp());
-
-class MyApp extends StatelessWidget {
- // This widget is the root of your application.
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- title: 'Flutter Demo',
- theme: ThemeData(
- // This is the theme of your application.
- //
- // Try running your application with "flutter run". You'll see the
- // application has a blue toolbar. Then, without quitting the app, try
- // changing the primarySwatch below to Colors.green and then invoke
- // "hot reload" (press "r" in the console where you ran "flutter run",
- // or simply save your changes to "hot reload" in a Flutter IDE).
- // Notice that the counter didn't reset back to zero; the application
- // is not restarted.
- primarySwatch: Colors.blue,
- ),
- home: MyHomePage(title: 'Flutter Demo Home Page'),
- );
- }
-}
-
-class MyHomePage extends StatefulWidget {
- MyHomePage({Key key, this.title}) : super(key: key);
-
- // This widget is the home page of your application. It is stateful, meaning
- // that it has a State object (defined below) that contains fields that affect
- // how it looks.
-
- // This class is the configuration for the state. It holds the values (in this
- // case the title) provided by the parent (in this case the App widget) and
- // used by the build method of the State. Fields in a Widget subclass are
- // always marked "final".
-
- final String title;
-
- @override
- _MyHomePageState createState() => _MyHomePageState();
-}
-
-class _MyHomePageState extends State {
- int _counter = 0;
-
- void _incrementCounter() {
- setState(() {
- // This call to setState tells the Flutter framework that something has
- // changed in this State, which causes it to rerun the build method below
- // so that the display can reflect the updated values. If we changed
- // _counter without calling setState(), then the build method would not be
- // called again, and so nothing would appear to happen.
- _counter++;
- });
- }
-
- @override
- Widget build(BuildContext context) {
- // This method is rerun every time setState is called, for instance as done
- // by the _incrementCounter method above.
- //
- // The Flutter framework has been optimized to make rerunning build methods
- // fast, so that you can just rebuild anything that needs updating rather
- // than having to individually change instances of widgets.
- return Scaffold(
- appBar: AppBar(
- // Here we take the value from the MyHomePage object that was created by
- // the App.build method, and use it to set our appbar title.
- title: Text(widget.title),
- ),
- body: Center(
- // Center is a layout widget. It takes a single child and positions it
- // in the middle of the parent.
- child: Column(
- // Column is also a layout widget. It takes a list of children and
- // arranges them vertically. By default, it sizes itself to fit its
- // children horizontally, and tries to be as tall as its parent.
- //
- // Invoke "debug painting" (press "p" in the console, choose the
- // "Toggle Debug Paint" action from the Flutter Inspector in Android
- // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
- // to see the wireframe for each widget.
- //
- // Column has various properties to control how it sizes itself and
- // how it positions its children. Here we use mainAxisAlignment to
- // center the children vertically; the main axis here is the vertical
- // axis because Columns are vertical (the cross axis would be
- // horizontal).
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Text(
- 'You have pushed the button this many times:',
- ),
- Text(
- '$_counter',
- style: Theme.of(context).textTheme.display1,
- ),
- ],
- ),
- ),
- floatingActionButton: FloatingActionButton(
- onPressed: _incrementCounter,
- tooltip: 'Increment',
- child: Icon(Icons.add),
- ), // This trailing comma makes auto-formatting nicer for build methods.
- );
- }
-}
diff --git a/rest-api-tutorial.md b/rest-api-tutorial.md
deleted file mode 100644
index 806edde..0000000
--- a/rest-api-tutorial.md
+++ /dev/null
@@ -1,107 +0,0 @@
-# REST API
-
-Is An Application program Interface (API) that uses HTTP requests to GET, PUT, POST and DELETE data.
-is based on representational state transfer (REST) technology, an architectural style and approach to communications often used in web services development.
-An API for a website is code that allows two software programs to communicate with each another . The API spells out the proper way for a developer to write a program requesting services from an operating system or other application.
-
-> ***A REST API defines a set of functions which developers can perform requests and receive responses via HTTP protocol.***.
-
-![Rest-Api](https://i.imgur.com/ZwQ2L6k.png)
-
-
-## Why?
-
-All the processing is done on the server side. So the server has to do more work.
-The data is not separated from the page.
-In REST API, you ask the API server what you need and it sends you just the information you ask for, no additional formatting is done in the server.
-There is no need for unnecessary processing in the server. So, the performance of your website and apps are naturally improved. Also, you can use the same data in your website, desktop app, Android and iOS apps.
-
-
-## How?
-### Create a New Project
-
-Create a new flutter project in Android Studio and name it as you like.
-
-![Rest-Api](https://i.imgur.com/tRsrVWO.png)
-
-### Making an API Request In Flutter
-
-In this API request we make an API call to:https://jsonplaceholder.typicode.com/posts.
-If you dont know what 'jsonplaceholder' is its a Fake Online Rest API for Testing and Prototyping.
-
-First include the http package in 'pubspec.yaml' file.
-Add this line under dependencies.
-
-```ruby
-dependencies:
- flutter:
- sdk: flutter
- http: ^0.12.0
-```
-Import the http package in your 'main.dart' file:
-
-```ruby
-import 'package:http/http.dart' as http;
-```
-
-Create a function getData() which will fetch the data from the API.
-
-```ruby
-Future getData(){
-
- }
-```
-Weโll be making an API call which can take some time to return a response. This situation calls for async.
-Basically, weโll need to wait till the api call completes and returns a result. As soon as it does, weโll display the list.
-Weโll make the api call using http object and wait for it to complete.
-
-```ruby
-Future getData() async {
- var response = await http.get(
- Uri.encodeFull("https://jsonplaceholder.typicode.com/users/1/albums"),
- headers: {"Accept": "application/json"});
-
- setState(() {
- data = json.decode(response.body);
- });
- return "Success";
- }
-```
-
-To decode the data you need to use:
-
-```ruby
-import 'dart:convert';
-```
-Now weโll need to add a listview to our flutter app.
-
-
-### Adding a ListView
-Next, weโll be adding a listview in our flutter app.If you donโt know how to create a listview in flutter,here you have the link to learn more about ListViews.
-https://pusher.com/tutorials/flutter-listviews
-
-
-Let's create the getList function that will show us the date or show us a message saying "Please Wait".
-
-
-```ruby
-Widget getList() {
- if (data == null || data.length < 1) {
- return Container(
- child: Center(
- child: Text("Please wait..."),
- ),
- );
- }
- return ListView.separated(
- itemCount: data?.length,
- itemBuilder: (BuildContext context, int index) {
- return getListItem(index);
- },
-```
-
-Just create a Text widget and add some styling as you like.
-
-## Conclusion
-
-This is a quick example of how to make an API call in Flutter.
diff --git a/tdd-example/README.md b/tdd-example/README.md
deleted file mode 100644
index a5c1640..0000000
--- a/tdd-example/README.md
+++ /dev/null
@@ -1 +0,0 @@
-GOTO: https://github.com/dwyl/flutter-counter-example
diff --git a/tdd_architeture/.metadata b/tdd_architeture/.metadata
deleted file mode 100644
index 5d1241e..0000000
--- a/tdd_architeture/.metadata
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file tracks properties of this Flutter project.
-# Used by Flutter tool to assess capabilities and perform upgrades etc.
-#
-# This file should be version controlled and should not be manually edited.
-
-version:
- revision: 9f5ff2306bb3e30b2b98eee79cd231b1336f41f4
- channel: stable
-
-project_type: app
diff --git a/tdd_architeture/README.md b/tdd_architeture/README.md
deleted file mode 100644
index cebf16a..0000000
--- a/tdd_architeture/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# clean_architeture_tdd
-
-A new Flutter application.
-
-## Getting Started
-
-This project is a starting point for a Flutter application.
-
-A few resources to get you started if this is your first Flutter project:
-
-- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
-- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
-
-For help getting started with Flutter, view our
-[online documentation](https://flutter.dev/docs), which offers tutorials,
-samples, guidance on mobile development, and a full API reference.
diff --git a/tdd_architeture/android/.gitignore b/tdd_architeture/android/.gitignore
deleted file mode 100644
index bc2100d..0000000
--- a/tdd_architeture/android/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-gradle-wrapper.jar
-/.gradle
-/captures/
-/gradlew
-/gradlew.bat
-/local.properties
-GeneratedPluginRegistrant.java
diff --git a/tdd_architeture/android/app/build.gradle b/tdd_architeture/android/app/build.gradle
deleted file mode 100644
index ee0832c..0000000
--- a/tdd_architeture/android/app/build.gradle
+++ /dev/null
@@ -1,61 +0,0 @@
-def localProperties = new Properties()
-def localPropertiesFile = rootProject.file('local.properties')
-if (localPropertiesFile.exists()) {
- 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')
-if (flutterVersionCode == null) {
- flutterVersionCode = '1'
-}
-
-def flutterVersionName = localProperties.getProperty('flutter.versionName')
-if (flutterVersionName == null) {
- flutterVersionName = '1.0'
-}
-
-apply plugin: 'com.android.application'
-apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
-
-android {
- compileSdkVersion 28
-
- lintOptions {
- disable 'InvalidPackage'
- }
-
- defaultConfig {
- // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId "com.martins.clean_architeture_tdd"
- minSdkVersion 16
- targetSdkVersion 28
- versionCode flutterVersionCode.toInteger()
- versionName flutterVersionName
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- 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
- }
- }
-}
-
-flutter {
- source '../..'
-}
-
-dependencies {
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'androidx.test:runner:1.1.1'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
-}
diff --git a/tdd_architeture/android/app/src/debug/AndroidManifest.xml b/tdd_architeture/android/app/src/debug/AndroidManifest.xml
deleted file mode 100644
index 476c23f..0000000
--- a/tdd_architeture/android/app/src/debug/AndroidManifest.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/tdd_architeture/android/app/src/main/AndroidManifest.xml b/tdd_architeture/android/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 8595530..0000000
--- a/tdd_architeture/android/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tdd_architeture/android/app/src/main/java/com/martins/clean_architeture_tdd/MainActivity.java b/tdd_architeture/android/app/src/main/java/com/martins/clean_architeture_tdd/MainActivity.java
deleted file mode 100644
index ec69222..0000000
--- a/tdd_architeture/android/app/src/main/java/com/martins/clean_architeture_tdd/MainActivity.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.martins.clean_architeture_tdd;
-
-import androidx.annotation.NonNull;
-import io.flutter.embedding.android.FlutterActivity;
-import io.flutter.embedding.engine.FlutterEngine;
-import io.flutter.plugins.GeneratedPluginRegistrant;
-
-public class MainActivity extends FlutterActivity {
- @Override
- public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
- GeneratedPluginRegistrant.registerWith(flutterEngine);
- }
-}
diff --git a/tdd_architeture/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/tdd_architeture/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index db77bb4..0000000
Binary files a/tdd_architeture/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/tdd_architeture/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/tdd_architeture/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 17987b7..0000000
Binary files a/tdd_architeture/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/tdd_architeture/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/tdd_architeture/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 09d4391..0000000
Binary files a/tdd_architeture/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/tdd_architeture/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tdd_architeture/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index d5f1c8d..0000000
Binary files a/tdd_architeture/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/tdd_architeture/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/tdd_architeture/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index 4d6372e..0000000
Binary files a/tdd_architeture/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/tdd_architeture/android/app/src/main/res/values/styles.xml b/tdd_architeture/android/app/src/main/res/values/styles.xml
deleted file mode 100644
index 00fa441..0000000
--- a/tdd_architeture/android/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
diff --git a/tdd_architeture/android/build.gradle b/tdd_architeture/android/build.gradle
deleted file mode 100644
index e0d7ae2..0000000
--- a/tdd_architeture/android/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-buildscript {
- repositories {
- google()
- jcenter()
- }
-
- dependencies {
- classpath 'com.android.tools.build:gradle:3.5.0'
- }
-}
-
-allprojects {
- repositories {
- google()
- jcenter()
- }
-}
-
-rootProject.buildDir = '../build'
-subprojects {
- project.buildDir = "${rootProject.buildDir}/${project.name}"
-}
-subprojects {
- project.evaluationDependsOn(':app')
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
diff --git a/tdd_architeture/android/gradle.properties b/tdd_architeture/android/gradle.properties
deleted file mode 100644
index 38c8d45..0000000
--- a/tdd_architeture/android/gradle.properties
+++ /dev/null
@@ -1,4 +0,0 @@
-org.gradle.jvmargs=-Xmx1536M
-android.enableR8=true
-android.useAndroidX=true
-android.enableJetifier=true
diff --git a/tdd_architeture/android/gradle/wrapper/gradle-wrapper.properties b/tdd_architeture/android/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 296b146..0000000
--- a/tdd_architeture/android/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Fri Jun 23 08:50:38 CEST 2017
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
diff --git a/tdd_architeture/android/settings.gradle b/tdd_architeture/android/settings.gradle
deleted file mode 100644
index 5a2f14f..0000000
--- a/tdd_architeture/android/settings.gradle
+++ /dev/null
@@ -1,15 +0,0 @@
-include ':app'
-
-def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
-
-def plugins = new Properties()
-def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
-if (pluginsFile.exists()) {
- pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
-}
-
-plugins.each { name, path ->
- def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
- include ":$name"
- project(":$name").projectDir = pluginDirectory
-}
diff --git a/tdd_architeture/clean_architeture_tdd/.gitignore b/tdd_architeture/clean_architeture_tdd/.gitignore
deleted file mode 100644
index ae1f183..0000000
--- a/tdd_architeture/clean_architeture_tdd/.gitignore
+++ /dev/null
@@ -1,37 +0,0 @@
-# Miscellaneous
-*.class
-*.log
-*.pyc
-*.swp
-.DS_Store
-.atom/
-.buildlog/
-.history
-.svn/
-
-# IntelliJ related
-*.iml
-*.ipr
-*.iws
-.idea/
-
-# The .vscode folder contains launch configuration and tasks you configure in
-# VS Code which you may wish to be included in version control, so this line
-# is commented out by default.
-#.vscode/
-
-# Flutter/Dart/Pub related
-**/doc/api/
-.dart_tool/
-.flutter-plugins
-.flutter-plugins-dependencies
-.packages
-.pub-cache/
-.pub/
-/build/
-
-# Web related
-lib/generated_plugin_registrant.dart
-
-# Exceptions to above rules.
-!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
diff --git a/tdd_architeture/clean_architeture_tdd/.metadata b/tdd_architeture/clean_architeture_tdd/.metadata
deleted file mode 100644
index 5d1241e..0000000
--- a/tdd_architeture/clean_architeture_tdd/.metadata
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file tracks properties of this Flutter project.
-# Used by Flutter tool to assess capabilities and perform upgrades etc.
-#
-# This file should be version controlled and should not be manually edited.
-
-version:
- revision: 9f5ff2306bb3e30b2b98eee79cd231b1336f41f4
- channel: stable
-
-project_type: app
diff --git a/tdd_architeture/ios/.gitignore b/tdd_architeture/ios/.gitignore
deleted file mode 100644
index e96ef60..0000000
--- a/tdd_architeture/ios/.gitignore
+++ /dev/null
@@ -1,32 +0,0 @@
-*.mode1v3
-*.mode2v3
-*.moved-aside
-*.pbxuser
-*.perspectivev3
-**/*sync/
-.sconsign.dblite
-.tags*
-**/.vagrant/
-**/DerivedData/
-Icon?
-**/Pods/
-**/.symlinks/
-profile
-xcuserdata
-**/.generated/
-Flutter/App.framework
-Flutter/Flutter.framework
-Flutter/Flutter.podspec
-Flutter/Generated.xcconfig
-Flutter/app.flx
-Flutter/app.zip
-Flutter/flutter_assets/
-Flutter/flutter_export_environment.sh
-ServiceDefinitions.json
-Runner/GeneratedPluginRegistrant.*
-
-# Exceptions to above rules.
-!default.mode1v3
-!default.mode2v3
-!default.pbxuser
-!default.perspectivev3
diff --git a/tdd_architeture/ios/Flutter/AppFrameworkInfo.plist b/tdd_architeture/ios/Flutter/AppFrameworkInfo.plist
deleted file mode 100644
index 6b4c0f7..0000000
--- a/tdd_architeture/ios/Flutter/AppFrameworkInfo.plist
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- App
- CFBundleIdentifier
- io.flutter.flutter.app
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- App
- CFBundlePackageType
- FMWK
- CFBundleShortVersionString
- 1.0
- CFBundleSignature
- ????
- CFBundleVersion
- 1.0
- MinimumOSVersion
- 8.0
-
-
diff --git a/tdd_architeture/ios/Flutter/Debug.xcconfig b/tdd_architeture/ios/Flutter/Debug.xcconfig
deleted file mode 100644
index e8efba1..0000000
--- a/tdd_architeture/ios/Flutter/Debug.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
-#include "Generated.xcconfig"
diff --git a/tdd_architeture/ios/Flutter/Release.xcconfig b/tdd_architeture/ios/Flutter/Release.xcconfig
deleted file mode 100644
index 399e934..0000000
--- a/tdd_architeture/ios/Flutter/Release.xcconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
-#include "Generated.xcconfig"
diff --git a/tdd_architeture/ios/Podfile b/tdd_architeture/ios/Podfile
deleted file mode 100644
index 98a90b8..0000000
--- a/tdd_architeture/ios/Podfile
+++ /dev/null
@@ -1,87 +0,0 @@
-# Uncomment this line to define a global platform for your project
-# platform :ios, '9.0'
-
-# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
-ENV['COCOAPODS_DISABLE_STATS'] = 'true'
-
-project 'Runner', {
- 'Debug' => :debug,
- 'Profile' => :release,
- 'Release' => :release,
-}
-
-def parse_KV_file(file, separator='=')
- file_abs_path = File.expand_path(file)
- if !File.exists? file_abs_path
- return [];
- end
- generated_key_values = {}
- skip_line_start_symbols = ["#", "/"]
- File.foreach(file_abs_path) do |line|
- next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
- plugin = line.split(pattern=separator)
- if plugin.length == 2
- podname = plugin[0].strip()
- path = plugin[1].strip()
- podpath = File.expand_path("#{path}", file_abs_path)
- generated_key_values[podname] = podpath
- else
- puts "Invalid plugin specification: #{line}"
- end
- end
- generated_key_values
-end
-
-target 'Runner' do
- # Flutter Pod
-
- copied_flutter_dir = File.join(__dir__, 'Flutter')
- copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
- copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
- unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
- # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
- # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
- # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
-
- generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
- unless File.exist?(generated_xcode_build_settings_path)
- raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
- end
- generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
- cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
-
- unless File.exist?(copied_framework_path)
- FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
- end
- unless File.exist?(copied_podspec_path)
- FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
- end
- end
-
- # Keep pod path relative so it can be checked into Podfile.lock.
- pod 'Flutter', :path => 'Flutter'
-
- # Plugin Pods
-
- # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
- # referring to absolute paths on developers' machines.
- system('rm -rf .symlinks')
- system('mkdir -p .symlinks/plugins')
- plugin_pods = parse_KV_file('../.flutter-plugins')
- plugin_pods.each do |name, path|
- symlink = File.join('.symlinks', 'plugins', name)
- File.symlink(path, symlink)
- pod name, :path => File.join(symlink, 'ios')
- end
-end
-
-# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
-install! 'cocoapods', :disable_input_output_paths => true
-
-post_install do |installer|
- installer.pods_project.targets.each do |target|
- target.build_configurations.each do |config|
- config.build_settings['ENABLE_BITCODE'] = 'NO'
- end
- end
-end
diff --git a/tdd_architeture/ios/Runner.xcodeproj/project.pbxproj b/tdd_architeture/ios/Runner.xcodeproj/project.pbxproj
deleted file mode 100644
index a1306f3..0000000
--- a/tdd_architeture/ios/Runner.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,511 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 46;
- objects = {
-
-/* Begin PBXBuildFile section */
- 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
- 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
- 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
- 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
- 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
- 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
- 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
- 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
- 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
-/* End PBXBuildFile section */
-
-/* Begin PBXCopyFilesBuildPhase section */
- 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
- files = (
- 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
- 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
- );
- name = "Embed Frameworks";
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXCopyFilesBuildPhase section */
-
-/* Begin PBXFileReference section */
- 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
- 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
- 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
- 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
- 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
- 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
- 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
- 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
- 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
- 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
- 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
- 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
- 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
- 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
-/* End PBXFileReference section */
-
-/* Begin PBXFrameworksBuildPhase section */
- 97C146EB1CF9000F007C117D /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
- 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- 9740EEB11CF90186004384FC /* Flutter */ = {
- isa = PBXGroup;
- children = (
- 3B80C3931E831B6300D905FE /* App.framework */,
- 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
- 9740EEBA1CF902C7004384FC /* Flutter.framework */,
- 9740EEB21CF90195004384FC /* Debug.xcconfig */,
- 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
- 9740EEB31CF90195004384FC /* Generated.xcconfig */,
- );
- name = Flutter;
- sourceTree = "";
- };
- 97C146E51CF9000F007C117D = {
- isa = PBXGroup;
- children = (
- 9740EEB11CF90186004384FC /* Flutter */,
- 97C146F01CF9000F007C117D /* Runner */,
- 97C146EF1CF9000F007C117D /* Products */,
- CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
- );
- sourceTree = "";
- };
- 97C146EF1CF9000F007C117D /* Products */ = {
- isa = PBXGroup;
- children = (
- 97C146EE1CF9000F007C117D /* Runner.app */,
- );
- name = Products;
- sourceTree = "";
- };
- 97C146F01CF9000F007C117D /* Runner */ = {
- isa = PBXGroup;
- children = (
- 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
- 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
- 97C146FA1CF9000F007C117D /* Main.storyboard */,
- 97C146FD1CF9000F007C117D /* Assets.xcassets */,
- 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
- 97C147021CF9000F007C117D /* Info.plist */,
- 97C146F11CF9000F007C117D /* Supporting Files */,
- 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
- 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
- );
- path = Runner;
- sourceTree = "";
- };
- 97C146F11CF9000F007C117D /* Supporting Files */ = {
- isa = PBXGroup;
- children = (
- 97C146F21CF9000F007C117D /* main.m */,
- );
- name = "Supporting Files";
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
- 97C146ED1CF9000F007C117D /* Runner */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
- buildPhases = (
- 9740EEB61CF901F6004384FC /* Run Script */,
- 97C146EA1CF9000F007C117D /* Sources */,
- 97C146EB1CF9000F007C117D /* Frameworks */,
- 97C146EC1CF9000F007C117D /* Resources */,
- 9705A1C41CF9048500538489 /* Embed Frameworks */,
- 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- );
- buildRules = (
- );
- dependencies = (
- );
- name = Runner;
- productName = Runner;
- productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
- productType = "com.apple.product-type.application";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- 97C146E61CF9000F007C117D /* Project object */ = {
- isa = PBXProject;
- attributes = {
- LastUpgradeCheck = 1020;
- ORGANIZATIONNAME = "The Chromium Authors";
- TargetAttributes = {
- 97C146ED1CF9000F007C117D = {
- CreatedOnToolsVersion = 7.3.1;
- };
- };
- };
- buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
- compatibilityVersion = "Xcode 3.2";
- developmentRegion = en;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- Base,
- );
- mainGroup = 97C146E51CF9000F007C117D;
- productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
- projectDirPath = "";
- projectRoot = "";
- targets = (
- 97C146ED1CF9000F007C117D /* Runner */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
- 97C146EC1CF9000F007C117D /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
- 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
- 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
- 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXShellScriptBuildPhase section */
- 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "Thin Binary";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
- };
- 9740EEB61CF901F6004384FC /* Run Script */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "Run Script";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
- };
-/* End PBXShellScriptBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
- 97C146EA1CF9000F007C117D /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
- 97C146F31CF9000F007C117D /* main.m in Sources */,
- 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXVariantGroup section */
- 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 97C146FB1CF9000F007C117D /* Base */,
- );
- name = Main.storyboard;
- sourceTree = "";
- };
- 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
- isa = PBXVariantGroup;
- children = (
- 97C147001CF9000F007C117D /* Base */,
- );
- name = LaunchScreen.storyboard;
- sourceTree = "";
- };
-/* End PBXVariantGroup section */
-
-/* Begin XCBuildConfiguration section */
- 249021D3217E4FDB00AE95B9 /* Profile */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- SUPPORTED_PLATFORMS = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- };
- name = Profile;
- };
- 249021D4217E4FDB00AE95B9 /* Profile */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- ENABLE_BITCODE = NO;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- LIBRARY_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.martins.cleanArchitetureTdd;
- PRODUCT_NAME = "$(TARGET_NAME)";
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = Profile;
- };
- 97C147031CF9000F007C117D /* Debug */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = dwarf;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_TESTABILITY = YES;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_PREPROCESSOR_DEFINITIONS = (
- "DEBUG=1",
- "$(inherited)",
- );
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
- MTL_ENABLE_DEBUG_INFO = YES;
- ONLY_ACTIVE_ARCH = YES;
- SDKROOT = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
- };
- name = Debug;
- };
- 97C147041CF9000F007C117D /* Release */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
- CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- GCC_C_LANGUAGE_STANDARD = gnu99;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
- MTL_ENABLE_DEBUG_INFO = NO;
- SDKROOT = iphoneos;
- SUPPORTED_PLATFORMS = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
- VALIDATE_PRODUCT = YES;
- };
- name = Release;
- };
- 97C147061CF9000F007C117D /* Debug */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- ENABLE_BITCODE = NO;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- LIBRARY_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.martins.cleanArchitetureTdd;
- PRODUCT_NAME = "$(TARGET_NAME)";
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = Debug;
- };
- 97C147071CF9000F007C117D /* Release */ = {
- isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
- ENABLE_BITCODE = NO;
- FRAMEWORK_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
- INFOPLIST_FILE = Runner/Info.plist;
- LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- LIBRARY_SEARCH_PATHS = (
- "$(inherited)",
- "$(PROJECT_DIR)/Flutter",
- );
- PRODUCT_BUNDLE_IDENTIFIER = com.martins.cleanArchitetureTdd;
- PRODUCT_NAME = "$(TARGET_NAME)";
- VERSIONING_SYSTEM = "apple-generic";
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 97C147031CF9000F007C117D /* Debug */,
- 97C147041CF9000F007C117D /* Release */,
- 249021D3217E4FDB00AE95B9 /* Profile */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 97C147061CF9000F007C117D /* Debug */,
- 97C147071CF9000F007C117D /* Release */,
- 249021D4217E4FDB00AE95B9 /* Profile */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
- };
- rootObject = 97C146E61CF9000F007C117D /* Project object */;
-}
diff --git a/tdd_architeture/ios/Runner.xcworkspace/contents.xcworkspacedata b/tdd_architeture/ios/Runner.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 1d526a1..0000000
--- a/tdd_architeture/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/tdd_architeture/ios/Runner/AppDelegate.h b/tdd_architeture/ios/Runner/AppDelegate.h
deleted file mode 100644
index 36e21bb..0000000
--- a/tdd_architeture/ios/Runner/AppDelegate.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#import
-#import
-
-@interface AppDelegate : FlutterAppDelegate
-
-@end
diff --git a/tdd_architeture/ios/Runner/AppDelegate.m b/tdd_architeture/ios/Runner/AppDelegate.m
deleted file mode 100644
index 70e8393..0000000
--- a/tdd_architeture/ios/Runner/AppDelegate.m
+++ /dev/null
@@ -1,13 +0,0 @@
-#import "AppDelegate.h"
-#import "GeneratedPluginRegistrant.h"
-
-@implementation AppDelegate
-
-- (BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- [GeneratedPluginRegistrant registerWithRegistry:self];
- // Override point for customization after application launch.
- return [super application:application didFinishLaunchingWithOptions:launchOptions];
-}
-
-@end
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index d36b1fa..0000000
--- a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,122 +0,0 @@
-{
- "images" : [
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "20x20",
- "idiom" : "iphone",
- "filename" : "Icon-App-20x20@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "iphone",
- "filename" : "Icon-App-29x29@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "iphone",
- "filename" : "Icon-App-40x40@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "60x60",
- "idiom" : "iphone",
- "filename" : "Icon-App-60x60@3x.png",
- "scale" : "3x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "20x20",
- "idiom" : "ipad",
- "filename" : "Icon-App-20x20@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "29x29",
- "idiom" : "ipad",
- "filename" : "Icon-App-29x29@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "40x40",
- "idiom" : "ipad",
- "filename" : "Icon-App-40x40@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@1x.png",
- "scale" : "1x"
- },
- {
- "size" : "76x76",
- "idiom" : "ipad",
- "filename" : "Icon-App-76x76@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "83.5x83.5",
- "idiom" : "ipad",
- "filename" : "Icon-App-83.5x83.5@2x.png",
- "scale" : "2x"
- },
- {
- "size" : "1024x1024",
- "idiom" : "ios-marketing",
- "filename" : "Icon-App-1024x1024@1x.png",
- "scale" : "1x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
deleted file mode 100644
index dc9ada4..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
deleted file mode 100644
index 28c6bf0..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
deleted file mode 100644
index 2ccbfd9..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
deleted file mode 100644
index f091b6b..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
deleted file mode 100644
index 4cde121..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
deleted file mode 100644
index d0ef06e..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
deleted file mode 100644
index dcdc230..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
deleted file mode 100644
index 2ccbfd9..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
deleted file mode 100644
index c8f9ed8..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
deleted file mode 100644
index a6d6b86..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
deleted file mode 100644
index a6d6b86..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
deleted file mode 100644
index 75b2d16..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
deleted file mode 100644
index c4df70d..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
deleted file mode 100644
index 6a84f41..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
deleted file mode 100644
index d0e1f58..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
deleted file mode 100644
index 0bedcf2..0000000
--- a/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "filename" : "LaunchImage.png",
- "scale" : "1x"
- },
- {
- "idiom" : "universal",
- "filename" : "LaunchImage@2x.png",
- "scale" : "2x"
- },
- {
- "idiom" : "universal",
- "filename" : "LaunchImage@3x.png",
- "scale" : "3x"
- }
- ],
- "info" : {
- "version" : 1,
- "author" : "xcode"
- }
-}
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
deleted file mode 100644
index 9da19ea..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
deleted file mode 100644
index 9da19ea..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
deleted file mode 100644
index 9da19ea..0000000
Binary files a/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ
diff --git a/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
deleted file mode 100644
index 89c2725..0000000
--- a/tdd_architeture/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Launch Screen Assets
-
-You can customize the launch screen with your own desired assets by replacing the image files in this directory.
-
-You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/tdd_architeture/ios/Runner/Base.lproj/LaunchScreen.storyboard b/tdd_architeture/ios/Runner/Base.lproj/LaunchScreen.storyboard
deleted file mode 100644
index f2e259c..0000000
--- a/tdd_architeture/ios/Runner/Base.lproj/LaunchScreen.storyboard
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tdd_architeture/ios/Runner/Base.lproj/Main.storyboard b/tdd_architeture/ios/Runner/Base.lproj/Main.storyboard
deleted file mode 100644
index f3c2851..0000000
--- a/tdd_architeture/ios/Runner/Base.lproj/Main.storyboard
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tdd_architeture/ios/Runner/main.m b/tdd_architeture/ios/Runner/main.m
deleted file mode 100644
index dff6597..0000000
--- a/tdd_architeture/ios/Runner/main.m
+++ /dev/null
@@ -1,9 +0,0 @@
-#import
-#import
-#import "AppDelegate.h"
-
-int main(int argc, char* argv[]) {
- @autoreleasepool {
- return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
- }
-}
diff --git a/tdd_architeture/lib/core/error/exceptions.dart b/tdd_architeture/lib/core/error/exceptions.dart
deleted file mode 100644
index e2a9463..0000000
--- a/tdd_architeture/lib/core/error/exceptions.dart
+++ /dev/null
@@ -1,8 +0,0 @@
-class ServerException implements Exception{
-
-
-}
-class CacheException implements Exception{
-
-
-}
\ No newline at end of file
diff --git a/tdd_architeture/lib/core/error/failures.dart b/tdd_architeture/lib/core/error/failures.dart
deleted file mode 100644
index 480793d..0000000
--- a/tdd_architeture/lib/core/error/failures.dart
+++ /dev/null
@@ -1,11 +0,0 @@
-import 'package:equatable/equatable.dart';
-
-abstract class Failure extends Equatable {
- @override
- List get props => [];
-}
-
-// General failures
-class ServerFailure extends Failure {}
-
-class CacheFailure extends Failure {}
\ No newline at end of file
diff --git a/tdd_architeture/lib/core/network/network_info.dart b/tdd_architeture/lib/core/network/network_info.dart
deleted file mode 100644
index ecc84e5..0000000
--- a/tdd_architeture/lib/core/network/network_info.dart
+++ /dev/null
@@ -1,14 +0,0 @@
-import 'package:data_connection_checker/data_connection_checker.dart';
-
-abstract class NetworkInfo {
- Future get isConnected;
-}
-
-class NetworkInfoImpl implements NetworkInfo {
- final DataConnectionChecker connectionChecker;
-
- NetworkInfoImpl(this.connectionChecker);
-
- @override
- Future get isConnected => connectionChecker.hasConnection;
-}
\ No newline at end of file
diff --git a/tdd_architeture/lib/core/platform/network_info.dart b/tdd_architeture/lib/core/platform/network_info.dart
deleted file mode 100644
index ecc84e5..0000000
--- a/tdd_architeture/lib/core/platform/network_info.dart
+++ /dev/null
@@ -1,14 +0,0 @@
-import 'package:data_connection_checker/data_connection_checker.dart';
-
-abstract class NetworkInfo {
- Future get isConnected;
-}
-
-class NetworkInfoImpl implements NetworkInfo {
- final DataConnectionChecker connectionChecker;
-
- NetworkInfoImpl(this.connectionChecker);
-
- @override
- Future get isConnected => connectionChecker.hasConnection;
-}
\ No newline at end of file
diff --git a/tdd_architeture/lib/core/usecases/usecase.dart b/tdd_architeture/lib/core/usecases/usecase.dart
deleted file mode 100644
index e09642b..0000000
--- a/tdd_architeture/lib/core/usecases/usecase.dart
+++ /dev/null
@@ -1,13 +0,0 @@
-import 'package:dartz/dartz.dart';
-import 'package:equatable/equatable.dart';
-
-import '../error/failures.dart';
-
-abstract class UseCase {
- Future> call(Params params);
-}
-
-class NoParams extends Equatable {
- @override
- List get props => [];
-}
\ No newline at end of file
diff --git a/tdd_architeture/lib/core/util/input_converter.dart b/tdd_architeture/lib/core/util/input_converter.dart
deleted file mode 100644
index aba4f1f..0000000
--- a/tdd_architeture/lib/core/util/input_converter.dart
+++ /dev/null
@@ -1,17 +0,0 @@
-
-import 'package:clean_architeture_tdd/core/error/failures.dart';
-import 'package:dartz/dartz.dart';
-
-class InputConverter {
- Either stringToUnsignedInteger(String str) {
- try {
- final integer = int.parse(str);
- if (integer < 0) throw FormatException();
- return Right(integer);
- } on FormatException {
- return Left(InvalidInputFailure());
- }
- }
-}
-
-class InvalidInputFailure extends Failure {}
\ No newline at end of file
diff --git a/tdd_architeture/lib/features/number_trivia/data/datasources/number_trivia_local_data_source.dart b/tdd_architeture/lib/features/number_trivia/data/datasources/number_trivia_local_data_source.dart
deleted file mode 100644
index 20dbe2d..0000000
--- a/tdd_architeture/lib/features/number_trivia/data/datasources/number_trivia_local_data_source.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-import 'dart:convert';
-import 'package:clean_architeture_tdd/core/error/exceptions.dart';
-import 'package:meta/meta.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-
-import '../models/number_trivia_model.dart';
-
-abstract class NumberTriviaLocalDataSource {
- /// Gets the cached [NumberTriviaModel] which was gotten the last time
- /// the user had an internet connection.
- ///
- /// Throws [CacheException] if no cached data is present.
- Future getLastNumberTrivia();
-
- Future cacheNumberTrivia(NumberTriviaModel triviaToCache);
-}
-
-const CACHED_NUMBER_TRIVIA = 'CACHED_NUMBER_TRIVIA';
-
-class NumberTriviaLocalDataSourceImpl implements NumberTriviaLocalDataSource {
- final SharedPreferences sharedPreferences;
-
- NumberTriviaLocalDataSourceImpl({@required this.sharedPreferences});
-
- @override
- Future getLastNumberTrivia() {
- final jsonString = sharedPreferences.getString(CACHED_NUMBER_TRIVIA);
- if (jsonString != null) {
- return Future.value(NumberTriviaModel.fromJson(json.decode(jsonString)));
- } else {
- throw CacheException();
- }
- }
-
- @override
- Future cacheNumberTrivia(NumberTriviaModel triviaToCache) {
- return sharedPreferences.setString(
- CACHED_NUMBER_TRIVIA,
- json.encode(triviaToCache.toJson()),
- );
- }
-}
\ No newline at end of file
diff --git a/tdd_architeture/lib/features/number_trivia/data/datasources/number_trivia_remote_data_source.dart b/tdd_architeture/lib/features/number_trivia/data/datasources/number_trivia_remote_data_source.dart
deleted file mode 100644
index c29f354..0000000
--- a/tdd_architeture/lib/features/number_trivia/data/datasources/number_trivia_remote_data_source.dart
+++ /dev/null
@@ -1,48 +0,0 @@
-import 'dart:convert';
-
-import 'package:http/http.dart' as http;
-import 'package:meta/meta.dart';
-
-import '../../../../core/error/exceptions.dart';
-import '../models/number_trivia_model.dart';
-
-abstract class NumberTriviaRemoteDataSource {
- /// Calls the http://numbersapi.com/{number} endpoint.
- ///
- /// Throws a [ServerException] for all error codes.
- Future getConcreteNumberTrivia(int number);
-
- /// Calls the http://numbersapi.com/random endpoint.
- ///
- /// Throws a [ServerException] for all error codes.
- Future getRandomNumberTrivia();
-}
-
-class NumberTriviaRemoteDataSourceImpl implements NumberTriviaRemoteDataSource {
- final http.Client client;
-
- NumberTriviaRemoteDataSourceImpl({@required this.client});
-
- @override
- Future getConcreteNumberTrivia(int number) =>
- _getTriviaFromUrl('http://numbersapi.com/$number');
-
- @override
- Future getRandomNumberTrivia() =>
- _getTriviaFromUrl('http://numbersapi.com/random');
-
- Future _getTriviaFromUrl(String url) async {
- final response = await client.get(
- url,
- headers: {
- 'Content-Type': 'application/json',
- },
- );
-
- if (response.statusCode == 200) {
- return NumberTriviaModel.fromJson(json.decode(response.body));
- } else {
- throw ServerException();
- }
- }
-}
\ No newline at end of file
diff --git a/tdd_architeture/lib/features/number_trivia/data/models/number_trivia_model.dart b/tdd_architeture/lib/features/number_trivia/data/models/number_trivia_model.dart
deleted file mode 100644
index 7b47ee3..0000000
--- a/tdd_architeture/lib/features/number_trivia/data/models/number_trivia_model.dart
+++ /dev/null
@@ -1,23 +0,0 @@
-import 'package:clean_architeture_tdd/features/number_trivia/domain/entities/number_trivia.dart';
-import 'package:meta/meta.dart';
-
-class NumberTriviaModel extends NumberTrivia {
- NumberTriviaModel({
- @required String text,
- @required int number,
- }) : super(text: text, number: number);
-
- factory NumberTriviaModel.fromJson(Map json) {
- return NumberTriviaModel(
- text: json['text'],
- number: (json['number'] as num).toInt(),
- );
- }
-
- Map toJson() {
- return {
- 'text': text,
- 'number': number,
- };
- }
-}
\ No newline at end of file
diff --git a/tdd_architeture/lib/features/number_trivia/data/repositories/number_trivia_repository_impl.dart b/tdd_architeture/lib/features/number_trivia/data/repositories/number_trivia_repository_impl.dart
deleted file mode 100644
index 596062c..0000000
--- a/tdd_architeture/lib/features/number_trivia/data/repositories/number_trivia_repository_impl.dart
+++ /dev/null
@@ -1,61 +0,0 @@
-import 'package:clean_architeture_tdd/features/number_trivia/domain/number_trivia_repository.dart';
-import 'package:dartz/dartz.dart';
-import 'package:meta/meta.dart';
-
-import '../../../../core/error/failures.dart';
-import '../../../../core/error/exceptions.dart';
-import '../../../../core/network/network_info.dart';
-import '../../domain/entities/number_trivia.dart';
-import '../datasources/number_trivia_local_data_source.dart';
-import '../datasources/number_trivia_remote_data_source.dart';
-
-typedef Future _ConcreteOrRandomChooser();
-
-class NumberTriviaRepositoryImpl implements NumberTriviaRepository {
- final NumberTriviaRemoteDataSource remoteDataSource;
- final NumberTriviaLocalDataSource localDataSource;
- final NetworkInfo networkInfo;
-
- NumberTriviaRepositoryImpl({
- @required this.remoteDataSource,
- @required this.localDataSource,
- @required this.networkInfo,
- });
-
- @override
- Future> getConcreteNumberTrivia(
- int number,
- ) async {
- return await _getTrivia(() {
- return remoteDataSource.getConcreteNumberTrivia(number);
- });
- }
-
- @override
- Future> getRandomNumberTrivia() async {
- return await _getTrivia(() {
- return remoteDataSource.getRandomNumberTrivia();
- });
- }
-
- Future> _getTrivia(
- _ConcreteOrRandomChooser getConcreteOrRandom,
- ) async {
- if (await networkInfo.isConnected) {
- try {
- final remoteTrivia = await getConcreteOrRandom();
- localDataSource.cacheNumberTrivia(remoteTrivia);
- return Right(remoteTrivia);
- } on ServerException {
- return Left(ServerFailure());
- }
- } else {
- try {
- final localTrivia = await localDataSource.getLastNumberTrivia();
- return Right(localTrivia);
- } on CacheException {
- return Left(CacheFailure());
- }
- }
- }
-}
\ No newline at end of file
diff --git a/tdd_architeture/lib/features/number_trivia/domain/entities/number_trivia.dart b/tdd_architeture/lib/features/number_trivia/domain/entities/number_trivia.dart
deleted file mode 100644
index 8e7e0ee..0000000
--- a/tdd_architeture/lib/features/number_trivia/domain/entities/number_trivia.dart
+++ /dev/null
@@ -1,15 +0,0 @@
-import 'package:equatable/equatable.dart';
-import 'package:meta/meta.dart';
-
-class NumberTrivia extends Equatable {
- final String text;
- final int number;
-
- NumberTrivia({
- @required this.text,
- @required this.number,
- });
-
- @override
- List get props => [text, number];
-}
diff --git a/tdd_architeture/lib/features/number_trivia/domain/number_trivia_repository.dart b/tdd_architeture/lib/features/number_trivia/domain/number_trivia_repository.dart
deleted file mode 100644
index 0e9bcc6..0000000
--- a/tdd_architeture/lib/features/number_trivia/domain/number_trivia_repository.dart
+++ /dev/null
@@ -1,8 +0,0 @@
-import 'package:clean_architeture_tdd/core/error/failures.dart';
-import 'package:clean_architeture_tdd/features/number_trivia/domain/entities/number_trivia.dart';
-import 'package:dartz/dartz.dart';
-
-abstract class NumberTriviaRepository{
- Future> getConcreteNumberTrivia(int number);
- Future> getRandomNumberTrivia();
-}
\ No newline at end of file
diff --git a/tdd_architeture/lib/features/number_trivia/domain/usecases/get_concrete_number_trivia.dart b/tdd_architeture/lib/features/number_trivia/domain/usecases/get_concrete_number_trivia.dart
deleted file mode 100644
index a65e070..0000000
--- a/tdd_architeture/lib/features/number_trivia/domain/usecases/get_concrete_number_trivia.dart
+++ /dev/null
@@ -1,30 +0,0 @@
-
-import 'package:clean_architeture_tdd/features/number_trivia/domain/number_trivia_repository.dart';
-import 'package:dartz/dartz.dart';
-import 'package:equatable/equatable.dart';
-import 'package:meta/meta.dart';
-
-import '../../../../core/error/failures.dart';
-import '../../../../core/usecases/usecase.dart';
-import '../entities/number_trivia.dart';
-
-
-class GetConcreteNumberTrivia implements UseCase