├── .flutter-plugins-dependencies
├── .gitignore
├── .metadata
├── LICENSE.md
├── README.md
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ └── com
│ │ │ └── musevisions
│ │ │ └── multiplecountersflutter
│ │ │ └── MainActivity.kt
│ │ └── res
│ │ ├── drawable
│ │ └── launch_background.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ └── values
│ │ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ ├── Flutter.podspec
│ └── Release.xcconfig
├── Podfile
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x.png
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ └── Icon-App-83.5x83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
├── lib
├── bottom_navigation.dart
├── common_widgets
│ ├── counter_list_tile.dart
│ ├── list_items_builder.dart
│ └── placeholder_content.dart
├── database.dart
├── main.dart
└── pages
│ ├── redux_page.dart
│ ├── scoped_model_page.dart
│ ├── set_state_page.dart
│ └── streams_page.dart
├── multiple_counters_flutter.iml
├── multiple_counters_flutter_android.iml
├── pubspec.lock
├── pubspec.yaml
├── screenshots
├── multiple_counters_tabs.png
└── poster-state-management.png
└── test
└── widget_test.dart
/.flutter-plugins-dependencies:
--------------------------------------------------------------------------------
1 | {"_info":"// This is a generated file; do not edit or check into version control.","dependencyGraph":[{"name":"cloud_firestore","dependencies":["firebase_core"]},{"name":"firebase_core","dependencies":[]},{"name":"firebase_database","dependencies":["firebase_core"]}]}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.lock
4 | *.log
5 | *.pyc
6 | *.swp
7 | .DS_Store
8 | .atom/
9 | .buildlog/
10 | .history
11 | .svn/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # Visual Studio Code related
20 | .vscode/
21 |
22 | # Flutter repo-specific
23 | /bin/cache/
24 | /bin/mingit/
25 | /dev/benchmarks/mega_gallery/
26 | /dev/bots/.recipe_deps
27 | /dev/bots/android_tools/
28 | /dev/docs/doc/
29 | /dev/docs/flutter.docs.zip
30 | /dev/docs/lib/
31 | /dev/docs/pubspec.yaml
32 | /packages/flutter/coverage/
33 | version
34 |
35 | # Flutter/Dart/Pub related
36 | **/doc/api/
37 | .dart_tool/
38 | .flutter-plugins
39 | .packages
40 | .pub-cache/
41 | .pub/
42 | build/
43 | flutter_*.png
44 | linked_*.ds
45 | unlinked.ds
46 | unlinked_spec.ds
47 |
48 | # Android related
49 | **/android/**/gradle-wrapper.jar
50 | **/android/.gradle
51 | **/android/captures/
52 | **/android/gradlew
53 | **/android/gradlew.bat
54 | **/android/local.properties
55 | **/android/**/GeneratedPluginRegistrant.java
56 | **/android/key.properties
57 | *.jks
58 |
59 | # iOS/XCode related
60 | **/ios/**/*.mode1v3
61 | **/ios/**/*.mode2v3
62 | **/ios/**/*.moved-aside
63 | **/ios/**/*.pbxuser
64 | **/ios/**/*.perspectivev3
65 | **/ios/**/*sync/
66 | **/ios/**/.sconsign.dblite
67 | **/ios/**/.tags*
68 | **/ios/**/.vagrant/
69 | **/ios/**/DerivedData/
70 | **/ios/**/Icon?
71 | **/ios/**/Pods/
72 | **/ios/**/.symlinks/
73 | **/ios/**/profile
74 | **/ios/**/xcuserdata
75 | **/ios/.generated/
76 | **/ios/Flutter/App.framework
77 | **/ios/Flutter/Flutter.framework
78 | **/ios/Flutter/Generated.xcconfig
79 | **/ios/Flutter/app.flx
80 | **/ios/Flutter/app.zip
81 | **/ios/Flutter/flutter_assets/
82 | **/ios/ServiceDefinitions.json
83 | **/ios/Runner/GeneratedPluginRegistrant.*
84 | **/ios/Flutter/flutter_export_environment.sh
85 |
86 | # Exceptions to above rules.
87 | !**/ios/**/default.mode1v3
88 | !**/ios/**/default.mode2v3
89 | !**/ios/**/default.pbxuser
90 | !**/ios/**/default.perspectivev3
91 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
92 |
93 | # Firebase configuration files
94 | ios/Runner/GoogleService-Info.plist
95 | android/app/google-services.json
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: f9bb4289e9fd861d70ae78bcc3a042ef1b35cc9d
8 | channel: beta
9 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 Andrea Bizzotto [bizz84@gmail.com](mailto:bizz84@gmail.com)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Flutter State Management
2 |
3 | This is a sample app showing four different approaches to managing state in Flutter:
4 |
5 | ### [`setState`](https://docs.flutter.io/flutter/widgets/State/setState.html) vs [`StreamBuilder`](https://docs.flutter.io/flutter/widgets/StreamBuilder-class.html) vs [`scoped_model`](https://pub.dartlang.org/packages/scoped_model) vs [`redux`](https://pub.dartlang.org/packages/redux)
6 |
7 | **Use case: manage multiple counters, synced with Firebase Database.**
8 |
9 | Watch my video for a full overview of the differences and tradeoffs between these techniques:
10 |
11 | [](https://youtu.be/HLop7s2sJ7Q)
12 |
13 | Supported tasks:
14 |
15 | - Show a list of counters
16 | - Add new counters
17 | - Increment or decrement existing counters
18 | - Remove counters (swipe left to dismiss)
19 |
20 | ## Database
21 |
22 | The app uses Firebase as a source of truth for the state of the counters. This allows the data to be **easily synced** across multiple clients. Realtime Database and Cloud Firestore are both supported (see `database.dart` class).
23 |
24 | **NOTE**: For simplicity, the whole database has public read/write access, and counters can't be set per-user. For a production app it would be more appropriate to set user access rules.
25 |
26 | ## State management
27 |
28 | The same functionality is replicated in four different pages accessible via the bottom navigation bar, using different state management techniques:
29 |
30 | * [`setState`](https://docs.flutter.io/flutter/widgets/State/setState.html)
31 | * [`StreamBuilder`](https://docs.flutter.io/flutter/widgets/StreamBuilder-class.html)
32 | * [`scoped_model`](https://pub.dartlang.org/packages/scoped_model)
33 | * [`redux`](https://pub.dartlang.org/packages/redux)
34 |
35 | ## Running the project
36 |
37 | You need to register the project with your own Firebase account.
38 |
39 | - Use `com.musevisions.multipleCountersFlutter` as your bundle / application ID when generating the Firebase project.
40 |
41 | - Download the `ios/Runner/GoogleService-Info.plist` and `android/app/google-services.json` files as needed.
42 |
43 | ### For more articles and video tutorials, check out [Coding With Flutter](https://codingwithflutter.com/).
44 |
45 | ### [License: MIT](LICENSE.md)
46 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.class
3 | .gradle
4 | /local.properties
5 | /.idea/workspace.xml
6 | /.idea/libraries
7 | .DS_Store
8 | /build
9 | /captures
10 | GeneratedPluginRegistrant.java
11 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | apply plugin: 'com.android.application'
15 | apply plugin: 'kotlin-android'
16 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
17 |
18 | android {
19 | compileSdkVersion 27
20 |
21 | sourceSets {
22 | main.java.srcDirs += 'src/main/kotlin'
23 | }
24 |
25 | lintOptions {
26 | disable 'InvalidPackage'
27 | }
28 |
29 | defaultConfig {
30 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
31 | applicationId "com.musevisions.multipleCountersFlutter"
32 | minSdkVersion 16
33 | targetSdkVersion 27
34 | versionCode 1
35 | versionName "1.0"
36 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
37 | }
38 |
39 | buildTypes {
40 | release {
41 | // TODO: Add your own signing config for the release build.
42 | // Signing with the debug keys for now, so `flutter run --release` works.
43 | signingConfig signingConfigs.debug
44 | }
45 | }
46 | }
47 |
48 | flutter {
49 | source '../..'
50 | }
51 |
52 | dependencies {
53 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
54 | testImplementation 'junit:junit:4.12'
55 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
56 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
57 | }
58 | apply plugin: 'com.google.gms.google-services'
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
15 |
19 |
26 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/musevisions/multiplecountersflutter/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.musevisions.multiplecountersflutter
2 |
3 | import android.os.Bundle
4 |
5 | import io.flutter.app.FlutterActivity
6 | import io.flutter.plugins.GeneratedPluginRegistrant
7 |
8 | class MainActivity(): FlutterActivity() {
9 | override fun onCreate(savedInstanceState: Bundle?) {
10 | super.onCreate(savedInstanceState)
11 | GeneratedPluginRegistrant.registerWith(this)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.1.51'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.0.1'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | classpath 'com.google.gms:google-services:4.0.0'
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | google()
18 | jcenter()
19 | }
20 | }
21 |
22 | rootProject.buildDir = '../build'
23 | subprojects {
24 | project.buildDir = "${rootProject.buildDir}/${project.name}"
25 | }
26 | subprojects {
27 | project.evaluationDependsOn(':app')
28 | }
29 |
30 | task clean(type: Delete) {
31 | delete rootProject.buildDir
32 | }
33 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 | GeneratedPluginRegistrant.h
13 | GeneratedPluginRegistrant.m
14 |
15 | .generated/
16 |
17 | *.pbxuser
18 | *.mode1v3
19 | *.mode2v3
20 | *.perspectivev3
21 |
22 | !default.pbxuser
23 | !default.mode1v3
24 | !default.mode2v3
25 | !default.perspectivev3
26 |
27 | xcuserdata
28 |
29 | *.moved-aside
30 |
31 | *.pyc
32 | *sync/
33 | Icon?
34 | .tags*
35 |
36 | /Flutter/app.flx
37 | /Flutter/app.zip
38 | /Flutter/flutter_assets/
39 | /Flutter/App.framework
40 | /Flutter/Flutter.framework
41 | /Flutter/Generated.xcconfig
42 | /ServiceDefinitions.json
43 |
44 | Pods/
45 | .symlinks/
46 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Flutter.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # NOTE: This podspec is NOT to be published. It is only used as a local source!
3 | #
4 |
5 | Pod::Spec.new do |s|
6 | s.name = 'Flutter'
7 | s.version = '1.0.0'
8 | s.summary = 'High-performance, high-fidelity mobile apps.'
9 | s.description = <<-DESC
10 | Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS.
11 | DESC
12 | s.homepage = 'https://flutter.io'
13 | s.license = { :type => 'MIT' }
14 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
15 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
16 | s.ios.deployment_target = '8.0'
17 | s.vendored_frameworks = 'Flutter.framework'
18 | end
19 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def parse_KV_file(file, separator='=')
14 | file_abs_path = File.expand_path(file)
15 | if !File.exists? file_abs_path
16 | return [];
17 | end
18 | generated_key_values = {}
19 | skip_line_start_symbols = ["#", "/"]
20 | File.foreach(file_abs_path) do |line|
21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
22 | plugin = line.split(pattern=separator)
23 | if plugin.length == 2
24 | podname = plugin[0].strip()
25 | path = plugin[1].strip()
26 | podpath = File.expand_path("#{path}", file_abs_path)
27 | generated_key_values[podname] = podpath
28 | else
29 | puts "Invalid plugin specification: #{line}"
30 | end
31 | end
32 | generated_key_values
33 | end
34 |
35 | target 'Runner' do
36 | use_frameworks!
37 | use_modular_headers!
38 |
39 | # Flutter Pod
40 |
41 | copied_flutter_dir = File.join(__dir__, 'Flutter')
42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
48 |
49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
50 | unless File.exist?(generated_xcode_build_settings_path)
51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
52 | end
53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
55 |
56 | unless File.exist?(copied_framework_path)
57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
58 | end
59 | unless File.exist?(copied_podspec_path)
60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
61 | end
62 | end
63 |
64 | # Keep pod path relative so it can be checked into Podfile.lock.
65 | pod 'Flutter', :path => 'Flutter'
66 |
67 | # Plugin Pods
68 |
69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
70 | # referring to absolute paths on developers' machines.
71 | system('rm -rf .symlinks')
72 | system('mkdir -p .symlinks/plugins')
73 | plugin_pods = parse_KV_file('../.flutter-plugins')
74 | plugin_pods.each do |name, path|
75 | symlink = File.join('.symlinks', 'plugins', name)
76 | File.symlink(path, symlink)
77 | pod name, :path => File.join(symlink, 'ios')
78 | end
79 | end
80 |
81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
82 | install! 'cocoapods', :disable_input_output_paths => true
83 |
84 | post_install do |installer|
85 | installer.pods_project.targets.each do |target|
86 | target.build_configurations.each do |config|
87 | config.build_settings['ENABLE_BITCODE'] = 'NO'
88 | end
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
14 | 589B45A78BDFE57628D78EDF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB7D1C6259736CBC92C4C0CA /* Pods_Runner.framework */; };
15 | 6576A03120D14C3700E29AB2 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6576A03020D14C3700E29AB2 /* GoogleService-Info.plist */; };
16 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
17 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
18 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
19 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
20 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
21 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
22 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
23 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
24 | /* End PBXBuildFile section */
25 |
26 | /* Begin PBXCopyFilesBuildPhase section */
27 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
28 | isa = PBXCopyFilesBuildPhase;
29 | buildActionMask = 2147483647;
30 | dstPath = "";
31 | dstSubfolderSpec = 10;
32 | files = (
33 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
34 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
35 | );
36 | name = "Embed Frameworks";
37 | runOnlyForDeploymentPostprocessing = 0;
38 | };
39 | /* End PBXCopyFilesBuildPhase section */
40 |
41 | /* Begin PBXFileReference section */
42 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
43 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
44 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
45 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
46 | 56D086B526C808656EFD53BA /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
47 | 6576A03020D14C3700E29AB2 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; };
48 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
49 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
50 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
51 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
52 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
53 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
54 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
55 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
56 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
57 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
58 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
59 | 9A4C628EFED4F6F2BB1667A0 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
60 | BB7D1C6259736CBC92C4C0CA /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
61 | /* End PBXFileReference section */
62 |
63 | /* Begin PBXFrameworksBuildPhase section */
64 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
65 | isa = PBXFrameworksBuildPhase;
66 | buildActionMask = 2147483647;
67 | files = (
68 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
69 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
70 | 589B45A78BDFE57628D78EDF /* Pods_Runner.framework in Frameworks */,
71 | );
72 | runOnlyForDeploymentPostprocessing = 0;
73 | };
74 | /* End PBXFrameworksBuildPhase section */
75 |
76 | /* Begin PBXGroup section */
77 | 191C10B034F186F177AC8B22 /* Frameworks */ = {
78 | isa = PBXGroup;
79 | children = (
80 | BB7D1C6259736CBC92C4C0CA /* Pods_Runner.framework */,
81 | );
82 | name = Frameworks;
83 | sourceTree = "";
84 | };
85 | 8B6E2CE2A32570C6F445BFC4 /* Pods */ = {
86 | isa = PBXGroup;
87 | children = (
88 | 9A4C628EFED4F6F2BB1667A0 /* Pods-Runner.debug.xcconfig */,
89 | 56D086B526C808656EFD53BA /* Pods-Runner.release.xcconfig */,
90 | );
91 | name = Pods;
92 | sourceTree = "";
93 | };
94 | 9740EEB11CF90186004384FC /* Flutter */ = {
95 | isa = PBXGroup;
96 | children = (
97 | 3B80C3931E831B6300D905FE /* App.framework */,
98 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
99 | 9740EEBA1CF902C7004384FC /* Flutter.framework */,
100 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
101 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
102 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
103 | );
104 | name = Flutter;
105 | sourceTree = "";
106 | };
107 | 97C146E51CF9000F007C117D = {
108 | isa = PBXGroup;
109 | children = (
110 | 9740EEB11CF90186004384FC /* Flutter */,
111 | 97C146F01CF9000F007C117D /* Runner */,
112 | 97C146EF1CF9000F007C117D /* Products */,
113 | 8B6E2CE2A32570C6F445BFC4 /* Pods */,
114 | 191C10B034F186F177AC8B22 /* Frameworks */,
115 | );
116 | sourceTree = "";
117 | };
118 | 97C146EF1CF9000F007C117D /* Products */ = {
119 | isa = PBXGroup;
120 | children = (
121 | 97C146EE1CF9000F007C117D /* Runner.app */,
122 | );
123 | name = Products;
124 | sourceTree = "";
125 | };
126 | 97C146F01CF9000F007C117D /* Runner */ = {
127 | isa = PBXGroup;
128 | children = (
129 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
130 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
131 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
132 | 97C147021CF9000F007C117D /* Info.plist */,
133 | 6576A03020D14C3700E29AB2 /* GoogleService-Info.plist */,
134 | 97C146F11CF9000F007C117D /* Supporting Files */,
135 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
136 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
137 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
138 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
139 | );
140 | path = Runner;
141 | sourceTree = "";
142 | };
143 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
144 | isa = PBXGroup;
145 | children = (
146 | );
147 | name = "Supporting Files";
148 | sourceTree = "";
149 | };
150 | /* End PBXGroup section */
151 |
152 | /* Begin PBXNativeTarget section */
153 | 97C146ED1CF9000F007C117D /* Runner */ = {
154 | isa = PBXNativeTarget;
155 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
156 | buildPhases = (
157 | C452F780B7E898EDD4FBB7C5 /* [CP] Check Pods Manifest.lock */,
158 | 9740EEB61CF901F6004384FC /* Run Script */,
159 | 97C146EA1CF9000F007C117D /* Sources */,
160 | 97C146EB1CF9000F007C117D /* Frameworks */,
161 | 97C146EC1CF9000F007C117D /* Resources */,
162 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
163 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
164 | 0CD9814F56F7546483D2C094 /* [CP] Embed Pods Frameworks */,
165 | );
166 | buildRules = (
167 | );
168 | dependencies = (
169 | );
170 | name = Runner;
171 | productName = Runner;
172 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
173 | productType = "com.apple.product-type.application";
174 | };
175 | /* End PBXNativeTarget section */
176 |
177 | /* Begin PBXProject section */
178 | 97C146E61CF9000F007C117D /* Project object */ = {
179 | isa = PBXProject;
180 | attributes = {
181 | LastUpgradeCheck = 0910;
182 | ORGANIZATIONNAME = "The Chromium Authors";
183 | TargetAttributes = {
184 | 97C146ED1CF9000F007C117D = {
185 | CreatedOnToolsVersion = 7.3.1;
186 | LastSwiftMigration = 0910;
187 | };
188 | };
189 | };
190 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
191 | compatibilityVersion = "Xcode 3.2";
192 | developmentRegion = English;
193 | hasScannedForEncodings = 0;
194 | knownRegions = (
195 | en,
196 | Base,
197 | );
198 | mainGroup = 97C146E51CF9000F007C117D;
199 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
200 | projectDirPath = "";
201 | projectRoot = "";
202 | targets = (
203 | 97C146ED1CF9000F007C117D /* Runner */,
204 | );
205 | };
206 | /* End PBXProject section */
207 |
208 | /* Begin PBXResourcesBuildPhase section */
209 | 97C146EC1CF9000F007C117D /* Resources */ = {
210 | isa = PBXResourcesBuildPhase;
211 | buildActionMask = 2147483647;
212 | files = (
213 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
214 | 6576A03120D14C3700E29AB2 /* GoogleService-Info.plist in Resources */,
215 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */,
216 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
217 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
218 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
219 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
220 | );
221 | runOnlyForDeploymentPostprocessing = 0;
222 | };
223 | /* End PBXResourcesBuildPhase section */
224 |
225 | /* Begin PBXShellScriptBuildPhase section */
226 | 0CD9814F56F7546483D2C094 /* [CP] Embed Pods Frameworks */ = {
227 | isa = PBXShellScriptBuildPhase;
228 | buildActionMask = 2147483647;
229 | files = (
230 | );
231 | inputPaths = (
232 | );
233 | name = "[CP] Embed Pods Frameworks";
234 | outputPaths = (
235 | );
236 | runOnlyForDeploymentPostprocessing = 0;
237 | shellPath = /bin/sh;
238 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
239 | showEnvVarsInLog = 0;
240 | };
241 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
242 | isa = PBXShellScriptBuildPhase;
243 | buildActionMask = 2147483647;
244 | files = (
245 | );
246 | inputPaths = (
247 | );
248 | name = "Thin Binary";
249 | outputPaths = (
250 | );
251 | runOnlyForDeploymentPostprocessing = 0;
252 | shellPath = /bin/sh;
253 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
254 | };
255 | 9740EEB61CF901F6004384FC /* Run Script */ = {
256 | isa = PBXShellScriptBuildPhase;
257 | buildActionMask = 2147483647;
258 | files = (
259 | );
260 | inputPaths = (
261 | );
262 | name = "Run Script";
263 | outputPaths = (
264 | );
265 | runOnlyForDeploymentPostprocessing = 0;
266 | shellPath = /bin/sh;
267 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
268 | };
269 | C452F780B7E898EDD4FBB7C5 /* [CP] Check Pods Manifest.lock */ = {
270 | isa = PBXShellScriptBuildPhase;
271 | buildActionMask = 2147483647;
272 | files = (
273 | );
274 | inputPaths = (
275 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
276 | "${PODS_ROOT}/Manifest.lock",
277 | );
278 | name = "[CP] Check Pods Manifest.lock";
279 | outputPaths = (
280 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
281 | );
282 | runOnlyForDeploymentPostprocessing = 0;
283 | shellPath = /bin/sh;
284 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
285 | showEnvVarsInLog = 0;
286 | };
287 | /* End PBXShellScriptBuildPhase section */
288 |
289 | /* Begin PBXSourcesBuildPhase section */
290 | 97C146EA1CF9000F007C117D /* Sources */ = {
291 | isa = PBXSourcesBuildPhase;
292 | buildActionMask = 2147483647;
293 | files = (
294 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
295 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
296 | );
297 | runOnlyForDeploymentPostprocessing = 0;
298 | };
299 | /* End PBXSourcesBuildPhase section */
300 |
301 | /* Begin PBXVariantGroup section */
302 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
303 | isa = PBXVariantGroup;
304 | children = (
305 | 97C146FB1CF9000F007C117D /* Base */,
306 | );
307 | name = Main.storyboard;
308 | sourceTree = "";
309 | };
310 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
311 | isa = PBXVariantGroup;
312 | children = (
313 | 97C147001CF9000F007C117D /* Base */,
314 | );
315 | name = LaunchScreen.storyboard;
316 | sourceTree = "";
317 | };
318 | /* End PBXVariantGroup section */
319 |
320 | /* Begin XCBuildConfiguration section */
321 | 97C147031CF9000F007C117D /* Debug */ = {
322 | isa = XCBuildConfiguration;
323 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
324 | buildSettings = {
325 | ALWAYS_SEARCH_USER_PATHS = NO;
326 | CLANG_ANALYZER_NONNULL = YES;
327 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
328 | CLANG_CXX_LIBRARY = "libc++";
329 | CLANG_ENABLE_MODULES = YES;
330 | CLANG_ENABLE_OBJC_ARC = YES;
331 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
332 | CLANG_WARN_BOOL_CONVERSION = YES;
333 | CLANG_WARN_COMMA = YES;
334 | CLANG_WARN_CONSTANT_CONVERSION = YES;
335 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
336 | CLANG_WARN_EMPTY_BODY = YES;
337 | CLANG_WARN_ENUM_CONVERSION = YES;
338 | CLANG_WARN_INFINITE_RECURSION = YES;
339 | CLANG_WARN_INT_CONVERSION = YES;
340 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
344 | CLANG_WARN_STRICT_PROTOTYPES = YES;
345 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
346 | CLANG_WARN_UNREACHABLE_CODE = YES;
347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
349 | COPY_PHASE_STRIP = NO;
350 | DEBUG_INFORMATION_FORMAT = dwarf;
351 | ENABLE_STRICT_OBJC_MSGSEND = YES;
352 | ENABLE_TESTABILITY = YES;
353 | GCC_C_LANGUAGE_STANDARD = gnu99;
354 | GCC_DYNAMIC_NO_PIC = NO;
355 | GCC_NO_COMMON_BLOCKS = YES;
356 | GCC_OPTIMIZATION_LEVEL = 0;
357 | GCC_PREPROCESSOR_DEFINITIONS = (
358 | "DEBUG=1",
359 | "$(inherited)",
360 | );
361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
363 | GCC_WARN_UNDECLARED_SELECTOR = YES;
364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
365 | GCC_WARN_UNUSED_FUNCTION = YES;
366 | GCC_WARN_UNUSED_VARIABLE = YES;
367 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
368 | MTL_ENABLE_DEBUG_INFO = YES;
369 | ONLY_ACTIVE_ARCH = YES;
370 | SDKROOT = iphoneos;
371 | TARGETED_DEVICE_FAMILY = "1,2";
372 | };
373 | name = Debug;
374 | };
375 | 97C147041CF9000F007C117D /* Release */ = {
376 | isa = XCBuildConfiguration;
377 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
378 | buildSettings = {
379 | ALWAYS_SEARCH_USER_PATHS = NO;
380 | CLANG_ANALYZER_NONNULL = YES;
381 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
382 | CLANG_CXX_LIBRARY = "libc++";
383 | CLANG_ENABLE_MODULES = YES;
384 | CLANG_ENABLE_OBJC_ARC = YES;
385 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
386 | CLANG_WARN_BOOL_CONVERSION = YES;
387 | CLANG_WARN_COMMA = YES;
388 | CLANG_WARN_CONSTANT_CONVERSION = YES;
389 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
390 | CLANG_WARN_EMPTY_BODY = YES;
391 | CLANG_WARN_ENUM_CONVERSION = YES;
392 | CLANG_WARN_INFINITE_RECURSION = YES;
393 | CLANG_WARN_INT_CONVERSION = YES;
394 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
395 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
397 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
398 | CLANG_WARN_STRICT_PROTOTYPES = YES;
399 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
400 | CLANG_WARN_UNREACHABLE_CODE = YES;
401 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
402 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
403 | COPY_PHASE_STRIP = NO;
404 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
405 | ENABLE_NS_ASSERTIONS = NO;
406 | ENABLE_STRICT_OBJC_MSGSEND = YES;
407 | GCC_C_LANGUAGE_STANDARD = gnu99;
408 | GCC_NO_COMMON_BLOCKS = YES;
409 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
410 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
411 | GCC_WARN_UNDECLARED_SELECTOR = YES;
412 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
413 | GCC_WARN_UNUSED_FUNCTION = YES;
414 | GCC_WARN_UNUSED_VARIABLE = YES;
415 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
416 | MTL_ENABLE_DEBUG_INFO = NO;
417 | SDKROOT = iphoneos;
418 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
419 | TARGETED_DEVICE_FAMILY = "1,2";
420 | VALIDATE_PRODUCT = YES;
421 | };
422 | name = Release;
423 | };
424 | 97C147061CF9000F007C117D /* Debug */ = {
425 | isa = XCBuildConfiguration;
426 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
427 | buildSettings = {
428 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
429 | CLANG_ENABLE_MODULES = YES;
430 | CURRENT_PROJECT_VERSION = 1;
431 | ENABLE_BITCODE = NO;
432 | FRAMEWORK_SEARCH_PATHS = (
433 | "$(inherited)",
434 | "$(PROJECT_DIR)/Flutter",
435 | );
436 | INFOPLIST_FILE = Runner/Info.plist;
437 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
438 | LIBRARY_SEARCH_PATHS = (
439 | "$(inherited)",
440 | "$(PROJECT_DIR)/Flutter",
441 | );
442 | PRODUCT_BUNDLE_IDENTIFIER = com.musevisions.multipleCountersFlutter;
443 | PRODUCT_NAME = "$(TARGET_NAME)";
444 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
445 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
446 | SWIFT_SWIFT3_OBJC_INFERENCE = On;
447 | SWIFT_VERSION = 4.0;
448 | VERSIONING_SYSTEM = "apple-generic";
449 | };
450 | name = Debug;
451 | };
452 | 97C147071CF9000F007C117D /* Release */ = {
453 | isa = XCBuildConfiguration;
454 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
455 | buildSettings = {
456 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
457 | CLANG_ENABLE_MODULES = YES;
458 | CURRENT_PROJECT_VERSION = 1;
459 | ENABLE_BITCODE = NO;
460 | FRAMEWORK_SEARCH_PATHS = (
461 | "$(inherited)",
462 | "$(PROJECT_DIR)/Flutter",
463 | );
464 | INFOPLIST_FILE = Runner/Info.plist;
465 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
466 | LIBRARY_SEARCH_PATHS = (
467 | "$(inherited)",
468 | "$(PROJECT_DIR)/Flutter",
469 | );
470 | PRODUCT_BUNDLE_IDENTIFIER = com.musevisions.multipleCountersFlutter;
471 | PRODUCT_NAME = "$(TARGET_NAME)";
472 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
473 | SWIFT_SWIFT3_OBJC_INFERENCE = On;
474 | SWIFT_VERSION = 4.0;
475 | VERSIONING_SYSTEM = "apple-generic";
476 | };
477 | name = Release;
478 | };
479 | /* End XCBuildConfiguration section */
480 |
481 | /* Begin XCConfigurationList section */
482 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
483 | isa = XCConfigurationList;
484 | buildConfigurations = (
485 | 97C147031CF9000F007C117D /* Debug */,
486 | 97C147041CF9000F007C117D /* Release */,
487 | );
488 | defaultConfigurationIsVisible = 0;
489 | defaultConfigurationName = Release;
490 | };
491 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
492 | isa = XCConfigurationList;
493 | buildConfigurations = (
494 | 97C147061CF9000F007C117D /* Debug */,
495 | 97C147071CF9000F007C117D /* Release */,
496 | );
497 | defaultConfigurationIsVisible = 0;
498 | defaultConfigurationName = Release;
499 | };
500 | /* End XCConfigurationList section */
501 | };
502 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
503 | }
504 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildSystemType
6 | Original
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 | import Firebase
4 |
5 | @UIApplicationMain
6 | @objc class AppDelegate: FlutterAppDelegate {
7 |
8 | override init() {
9 | super.init()
10 | FirebaseApp.configure()
11 | }
12 |
13 | override func application(
14 | _ application: UIApplication,
15 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
16 | ) -> Bool {
17 | GeneratedPluginRegistrant.register(with: self)
18 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | 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.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | multiple_counters_flutter
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
--------------------------------------------------------------------------------
/lib/bottom_navigation.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:redux/redux.dart';
3 | import 'package:flutter_redux/flutter_redux.dart';
4 | import 'package:scoped_model/scoped_model.dart';
5 | import 'package:multiple_counters_flutter/database.dart';
6 | import 'package:multiple_counters_flutter/pages/redux_page.dart';
7 | import 'package:multiple_counters_flutter/pages/scoped_model_page.dart';
8 | import 'package:multiple_counters_flutter/pages/set_state_page.dart';
9 | import 'package:multiple_counters_flutter/pages/streams_page.dart';
10 |
11 | enum TabItem {
12 | setState,
13 | streams,
14 | scoped,
15 | redux,
16 | }
17 |
18 | String tabItemName(TabItem tabItem) {
19 | switch (tabItem) {
20 | case TabItem.setState:
21 | return "setState";
22 | case TabItem.streams:
23 | return "streams";
24 | case TabItem.scoped:
25 | return "scoped";
26 | case TabItem.redux:
27 | return "redux";
28 | }
29 | return null;
30 | }
31 |
32 | class BottomNavigation extends StatefulWidget {
33 | @override
34 | State createState() => BottomNavigationState();
35 | }
36 |
37 | class BottomNavigationState extends State {
38 | TabItem currentItem = TabItem.setState;
39 |
40 | _onSelectTab(int index) {
41 | switch (index) {
42 | case 0:
43 | _updateCurrentItem(TabItem.setState);
44 | break;
45 | case 1:
46 | _updateCurrentItem(TabItem.streams);
47 | break;
48 | case 2:
49 | _updateCurrentItem(TabItem.scoped);
50 | break;
51 | case 3:
52 | _updateCurrentItem(TabItem.redux);
53 | break;
54 | }
55 | }
56 |
57 | _updateCurrentItem(TabItem item) {
58 | setState(() {
59 | currentItem = item;
60 | });
61 | }
62 |
63 | @override
64 | Widget build(BuildContext context) {
65 | return Scaffold(
66 | body: _buildBody(),
67 | bottomNavigationBar: _buildBottomNavigationBar(),
68 | );
69 | }
70 |
71 | Widget _buildBody() {
72 | var database = AppFirestore();
73 | var stream = database.countersStream();
74 | switch (currentItem) {
75 | case TabItem.setState:
76 | return SetStatePage(database: database, stream: stream);
77 | case TabItem.streams:
78 | return StreamsPage(database: database, stream: stream);
79 | case TabItem.scoped:
80 | return ScopedModel(
81 | model: CountersModel(stream: stream),
82 | child: ScopedModelPage(database: database),
83 | );
84 | case TabItem.redux:
85 | var middleware = CountersMiddleware(database: database, stream: stream);
86 | var store = Store(
87 | reducer,
88 | initialState: ReduxModel(counters: null),
89 | middleware: [ middleware ],
90 | );
91 | middleware.listen(store);
92 | return StoreProvider(
93 | store: store,
94 | child: ReduxPage(),
95 | );
96 | }
97 | return Container();
98 | }
99 |
100 | Widget _buildBottomNavigationBar() {
101 | return BottomNavigationBar(
102 | type: BottomNavigationBarType.fixed,
103 | items: [
104 | _buildItem(icon: Icons.adjust, tabItem: TabItem.setState),
105 | _buildItem(icon: Icons.clear_all, tabItem: TabItem.streams),
106 | _buildItem(icon: Icons.arrow_downward, tabItem: TabItem.scoped),
107 | _buildItem(icon: Icons.settings_input_component, tabItem: TabItem.redux),
108 | ],
109 | onTap: _onSelectTab,
110 | );
111 | }
112 |
113 | BottomNavigationBarItem _buildItem({IconData icon, TabItem tabItem}) {
114 | String text = tabItemName(tabItem);
115 | return BottomNavigationBarItem(
116 | icon: Icon(
117 | icon,
118 | color: _colorTabMatching(item: tabItem),
119 | ),
120 | title: Text(
121 | text,
122 | style: TextStyle(
123 | color: _colorTabMatching(item: tabItem),
124 | ),
125 | ),
126 | );
127 | }
128 |
129 | Color _colorTabMatching({TabItem item}) {
130 | return currentItem == item ? Theme.of(context).primaryColor : Colors.grey;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/lib/common_widgets/counter_list_tile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:multiple_counters_flutter/database.dart';
3 |
4 | class CounterListTile extends StatelessWidget {
5 | CounterListTile({this.key, this.counter, this.onDecrement, this.onIncrement, this.onDismissed});
6 | final Key key;
7 | final Counter counter;
8 | final ValueChanged onDecrement;
9 | final ValueChanged onIncrement;
10 | final ValueChanged onDismissed;
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | return Dismissible(
15 | background: Container(color: Colors.red),
16 | key: key,
17 | direction: DismissDirection.endToStart,
18 | onDismissed: (direction) => onDismissed(counter),
19 | child: ListTile(
20 | title: Text(
21 | '${counter.value}',
22 | style: TextStyle(fontSize: 48.0),
23 | ),
24 | subtitle: Text(
25 | '${counter.id}',
26 | style: TextStyle(fontSize: 16.0),
27 | ),
28 | trailing: Row(
29 | mainAxisSize: MainAxisSize.min,
30 | children: [
31 | CounterActionButton(
32 | iconData: Icons.remove,
33 | onPressed: () => onDecrement(counter),
34 | ),
35 | SizedBox(width: 8.0),
36 | CounterActionButton(
37 | iconData: Icons.add,
38 | onPressed: () => onIncrement(counter),
39 | ),
40 | ],
41 | ),
42 | ),
43 | );
44 | }
45 | }
46 |
47 | class CounterActionButton extends StatelessWidget {
48 | CounterActionButton({this.iconData, this.onPressed});
49 | final VoidCallback onPressed;
50 | final IconData iconData;
51 | @override
52 | Widget build(BuildContext context) {
53 | return CircleAvatar(
54 | radius: 28.0,
55 | backgroundColor: Theme.of(context).primaryColor,
56 | child: IconButton(
57 | icon: Icon(iconData, size: 28.0),
58 | color: Colors.black,
59 | onPressed: onPressed,
60 | ),
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/common_widgets/list_items_builder.dart:
--------------------------------------------------------------------------------
1 |
2 | import 'package:flutter/material.dart';
3 | import 'package:multiple_counters_flutter/common_widgets/placeholder_content.dart';
4 |
5 | typedef Widget ItemWidgetBuilder(BuildContext context, T item);
6 |
7 | class ListItemsBuilder extends StatelessWidget {
8 | ListItemsBuilder({this.items, this.itemBuilder});
9 | final List items;
10 | final ItemWidgetBuilder itemBuilder;
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | if (items != null) {
15 | if (items.length > 0) {
16 | return _buildList();
17 | } else {
18 | return PlaceholderContent();
19 | }
20 | } else {
21 | return Center(child: CircularProgressIndicator());
22 | }
23 | }
24 |
25 | Widget _buildList() {
26 | return ListView.builder(
27 | itemCount: items.length,
28 | itemBuilder: (context, index) {
29 | return itemBuilder(context, items[index]);
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/common_widgets/placeholder_content.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class PlaceholderContent extends StatelessWidget {
4 | PlaceholderContent({
5 | this.title: 'Nothing Here',
6 | this.message: 'Add a new item to get started.',
7 | });
8 | final String title;
9 | final String message;
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Center(
14 | child: Column(
15 | mainAxisAlignment: MainAxisAlignment.center,
16 | crossAxisAlignment: CrossAxisAlignment.center,
17 | children: [
18 | Text(title,
19 | style: TextStyle(fontSize: 32.0, color: Colors.black54),
20 | textAlign: TextAlign.center),
21 | Text(message,
22 | style: TextStyle(fontSize: 16.0, color: Colors.black54),
23 | textAlign: TextAlign.center),
24 | ]));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/database.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'package:cloud_firestore/cloud_firestore.dart';
3 | import 'package:firebase_database/firebase_database.dart';
4 |
5 | class Counter {
6 | Counter({this.id, this.value});
7 | int id;
8 | int value;
9 | }
10 |
11 | abstract class Database {
12 | Future createCounter();
13 | Future setCounter(Counter counter);
14 | Future deleteCounter(Counter counter);
15 | Stream> countersStream();
16 | }
17 |
18 | // Realtime Database
19 | class AppDatabase implements Database {
20 | Future createCounter() async {
21 | int now = DateTime.now().millisecondsSinceEpoch;
22 | Counter counter = Counter(id: now, value: 0);
23 | await setCounter(counter);
24 | }
25 |
26 | Future setCounter(Counter counter) async {
27 | DatabaseReference databaseReference = _databaseReference(counter);
28 | await databaseReference.set(counter.value);
29 | }
30 |
31 | Future deleteCounter(Counter counter) async {
32 | DatabaseReference databaseReference = _databaseReference(counter);
33 | await databaseReference.remove();
34 | }
35 |
36 | DatabaseReference _databaseReference(Counter counter) {
37 | var path = '$rootPath/${counter.id}';
38 | return FirebaseDatabase.instance.reference().child(path);
39 | }
40 |
41 | Stream> countersStream() {
42 | return _DatabaseStream>(
43 | apiPath: rootPath,
44 | parser: _DatabaseCountersParser(),
45 | ).stream;
46 | }
47 |
48 | static final String rootPath = 'counters';
49 | }
50 |
51 | class _DatabaseStream {
52 | _DatabaseStream({String apiPath, DatabaseNodeParser parser}) {
53 | FirebaseDatabase firebaseDatabase = FirebaseDatabase.instance;
54 | DatabaseReference databaseReference =
55 | firebaseDatabase.reference().child(apiPath);
56 | var eventStream = databaseReference.onValue;
57 | stream = eventStream.map((event) => parser.parse(event));
58 | }
59 |
60 | Stream stream;
61 | }
62 |
63 | abstract class DatabaseNodeParser {
64 | T parse(Event event);
65 | }
66 |
67 | class _DatabaseCountersParser implements DatabaseNodeParser> {
68 | List parse(Event event) {
69 | Map values = event.snapshot.value;
70 | if (values != null) {
71 | Iterable keys = values.keys.cast();
72 |
73 | var counters = keys
74 | .map((key) => Counter(id: int.parse(key), value: values[key]))
75 | .toList();
76 | counters.sort((lhs, rhs) => rhs.id.compareTo(lhs.id));
77 | return counters;
78 | } else {
79 | return [];
80 | }
81 | }
82 | }
83 |
84 | // Cloud Firestore
85 | class AppFirestore implements Database {
86 | Future createCounter() async {
87 | int now = DateTime.now().millisecondsSinceEpoch;
88 | Counter counter = Counter(id: now, value: 0);
89 | await setCounter(counter);
90 | }
91 |
92 | Future setCounter(Counter counter) async {
93 | _documentReference(counter).setData({
94 | 'value': counter.value,
95 | });
96 | }
97 |
98 | Future deleteCounter(Counter counter) async {
99 | _documentReference(counter).delete();
100 | }
101 |
102 | Stream> countersStream() {
103 | return _FirestoreStream>(
104 | apiPath: rootPath,
105 | parser: FirestoreCountersParser(),
106 | ).stream;
107 | }
108 |
109 | DocumentReference _documentReference(Counter counter) {
110 | return Firestore.instance.collection(rootPath).document('${counter.id}');
111 | }
112 |
113 | static final String rootPath = 'counters';
114 | }
115 |
116 | abstract class FirestoreNodeParser {
117 | T parse(QuerySnapshot querySnapshot);
118 | }
119 |
120 | class FirestoreCountersParser extends FirestoreNodeParser> {
121 | List parse(QuerySnapshot querySnapshot) {
122 | var counters = querySnapshot.documents.map((documentSnapshot) {
123 | return Counter(
124 | id: int.parse(documentSnapshot.documentID),
125 | value: documentSnapshot['value'],
126 | );
127 | }).toList();
128 | counters.sort((lhs, rhs) => rhs.id.compareTo(lhs.id));
129 | return counters;
130 | }
131 | }
132 |
133 | class _FirestoreStream {
134 | _FirestoreStream({String apiPath, FirestoreNodeParser parser}) {
135 | CollectionReference collectionReference =
136 | Firestore.instance.collection(apiPath);
137 | Stream snapshots = collectionReference.snapshots();
138 | stream = snapshots.map((snapshot) => parser.parse(snapshot));
139 | }
140 |
141 | Stream stream;
142 | }
143 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:multiple_counters_flutter/bottom_navigation.dart';
3 |
4 | void main() => runApp(new MyApp());
5 |
6 | class MyApp extends StatelessWidget {
7 | @override
8 | Widget build(BuildContext context) {
9 | return MaterialApp(
10 | title: 'Multiple counters',
11 | theme: ThemeData(
12 | primarySwatch: Colors.orange,
13 | ),
14 | home: BottomNavigation(),
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/pages/redux_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:redux/redux.dart';
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:multiple_counters_flutter/common_widgets/counter_list_tile.dart';
7 | import 'package:multiple_counters_flutter/common_widgets/list_items_builder.dart';
8 | import 'package:multiple_counters_flutter/database.dart';
9 |
10 |
11 | // Model
12 | class ReduxModel {
13 | ReduxModel({this.counters});
14 | List counters;
15 | }
16 |
17 | // Actions
18 | class CreateCounterAction {
19 |
20 | }
21 |
22 | class IncrementCounterAction {
23 | IncrementCounterAction({this.counter});
24 | Counter counter;
25 | }
26 |
27 | class DecrementCounterAction {
28 | DecrementCounterAction({this.counter});
29 | Counter counter;
30 | }
31 |
32 | class DeleteCounterAction {
33 | DeleteCounterAction({this.counter});
34 | Counter counter;
35 | }
36 |
37 | class UpdateCountersAction {
38 | UpdateCountersAction({this.counters});
39 | List counters;
40 | }
41 |
42 | // Middleware
43 | class CountersMiddleware extends MiddlewareClass {
44 | CountersMiddleware({this.database, this.stream});
45 | final Database database;
46 | final Stream> stream;
47 |
48 | void call(Store store, dynamic action, NextDispatcher next) {
49 | if (action is CreateCounterAction) {
50 | database.createCounter();
51 | }
52 | if (action is IncrementCounterAction) {
53 | Counter counter =
54 | Counter(id: action.counter.id, value: action.counter.value + 1);
55 | database.setCounter(counter);
56 | }
57 | if (action is DecrementCounterAction) {
58 | Counter counter =
59 | Counter(id: action.counter.id, value: action.counter.value - 1);
60 | database.setCounter(counter);
61 | }
62 | if (action is DeleteCounterAction) {
63 | database.deleteCounter(action.counter);
64 | }
65 | next(action);
66 | }
67 |
68 | void listen(Store store) {
69 | stream.listen((counters) {
70 | store.dispatch(UpdateCountersAction(counters: counters));
71 | });
72 | }
73 | }
74 |
75 | // Reducer
76 | ReduxModel reducer(ReduxModel model, dynamic action) {
77 | if (action is UpdateCountersAction) {
78 | return ReduxModel(counters: action.counters);
79 | }
80 | // Special handling to ensure counter is removed immediately after Dismissable is dismissed.
81 | if (action is DeleteCounterAction) {
82 | List counters = model.counters;
83 | counters.remove(action.counter);
84 | return ReduxModel(counters: counters);
85 | }
86 | return model;
87 | }
88 |
89 | // Page
90 | class ReduxPage extends StatelessWidget {
91 | void _createCounter(Store store) async {
92 | store.dispatch(CreateCounterAction());
93 | }
94 |
95 | void _increment(Store store, Counter counter) async {
96 | store.dispatch(IncrementCounterAction(counter: counter));
97 | }
98 |
99 | void _decrement(Store store, Counter counter) async {
100 | store.dispatch(DecrementCounterAction(counter: counter));
101 | }
102 |
103 | void _delete(Store store, Counter counter) async {
104 | store.dispatch(DeleteCounterAction(counter: counter));
105 | }
106 |
107 | @override
108 | Widget build(BuildContext context) {
109 | return Scaffold(
110 | appBar: AppBar(
111 | title: Text('Redux'),
112 | elevation: 1.0,
113 | ),
114 | body: _buildContent(),
115 | floatingActionButton: FloatingActionButton(
116 | child: Icon(Icons.add),
117 | onPressed: () => _createCounter(StoreProvider.of(context)),
118 | ),
119 | );
120 | }
121 |
122 | Widget _buildContent() {
123 | return StoreBuilder(
124 | builder: (context, Store store) {
125 | ReduxModel model = store.state;
126 | return ListItemsBuilder(
127 | items: model.counters,
128 | itemBuilder: (context, counter) {
129 | return CounterListTile(
130 | key: Key('counter-${counter.id}'),
131 | counter: counter,
132 | onDecrement: (counter) => _decrement(store, counter),
133 | onIncrement: (counter) => _increment(store, counter),
134 | onDismissed: (counter) => _delete(store, counter),
135 | );
136 | },
137 | );
138 | });
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/lib/pages/scoped_model_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:multiple_counters_flutter/common_widgets/counter_list_tile.dart';
5 | import 'package:multiple_counters_flutter/common_widgets/list_items_builder.dart';
6 | import 'package:multiple_counters_flutter/database.dart';
7 | import 'package:scoped_model/scoped_model.dart';
8 |
9 | class CountersModel extends Model {
10 | CountersModel({Stream> stream}) {
11 | stream.listen((counters) {
12 | this.counters = counters;
13 | notifyListeners();
14 | });
15 | }
16 |
17 | List counters;
18 | }
19 |
20 | class ScopedModelPage extends StatelessWidget {
21 | ScopedModelPage({this.database});
22 | final Database database;
23 |
24 | void _createCounter() async {
25 | await database.createCounter();
26 | }
27 |
28 | void _increment(Counter counter) async {
29 | counter.value++;
30 | await database.setCounter(counter);
31 | }
32 |
33 | void _decrement(Counter counter) async {
34 | counter.value--;
35 | await database.setCounter(counter);
36 | }
37 |
38 | void _delete(Counter counter) async {
39 | await database.deleteCounter(counter);
40 | }
41 |
42 | @override
43 | Widget build(BuildContext context) {
44 | return Scaffold(
45 | appBar: AppBar(
46 | title: Text('Scoped model'),
47 | elevation: 1.0,
48 | ),
49 | body: _buildContent(),
50 | floatingActionButton: FloatingActionButton(
51 | child: Icon(Icons.add),
52 | onPressed: _createCounter,
53 | ),
54 | );
55 | }
56 |
57 | Widget _buildContent() {
58 | return ScopedModelDescendant(
59 | builder: (context, child, model) {
60 | return ListItemsBuilder(
61 | items: model.counters,
62 | itemBuilder: (context, counter) {
63 | return CounterListTile(
64 | key: Key('counter-${counter.id}'),
65 | counter: counter,
66 | onDecrement: _decrement,
67 | onIncrement: _increment,
68 | onDismissed: _delete,
69 | );
70 | });
71 | });
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/lib/pages/set_state_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:multiple_counters_flutter/common_widgets/counter_list_tile.dart';
5 | import 'package:multiple_counters_flutter/common_widgets/list_items_builder.dart';
6 | import 'package:multiple_counters_flutter/database.dart';
7 |
8 | class SetStatePage extends StatefulWidget {
9 | SetStatePage({this.database, this.stream});
10 | final Database database;
11 | final Stream> stream;
12 |
13 | @override
14 | State createState() => SetStatePageState();
15 | }
16 |
17 | class SetStatePageState extends State {
18 | List _counters;
19 |
20 | StreamSubscription _subscription;
21 |
22 | @override
23 | void initState() {
24 | super.initState();
25 | _subscription = widget.stream.listen((counters) {
26 | setState(() {
27 | _counters = counters;
28 | });
29 | });
30 | }
31 |
32 | @override
33 | void dispose() {
34 | super.dispose();
35 | _subscription.cancel();
36 | }
37 |
38 | void _createCounter() async {
39 | await widget.database.createCounter();
40 | }
41 |
42 | void _increment(Counter counter) async {
43 | counter.value++;
44 | await widget.database.setCounter(counter);
45 | }
46 |
47 | void _decrement(Counter counter) async {
48 | counter.value--;
49 | await widget.database.setCounter(counter);
50 | }
51 |
52 | void _delete(Counter counter) async {
53 | await widget.database.deleteCounter(counter);
54 | }
55 |
56 | @override
57 | Widget build(BuildContext context) {
58 | return Scaffold(
59 | appBar: AppBar(
60 | title: Text('setState'),
61 | elevation: 1.0,
62 | ),
63 | body: _buildContent(),
64 | floatingActionButton: FloatingActionButton(
65 | child: Icon(Icons.add),
66 | onPressed: _createCounter,
67 | ),
68 | );
69 | }
70 |
71 | Widget _buildContent() {
72 | return ListItemsBuilder(
73 | items: _counters,
74 | itemBuilder: (context, counter) {
75 | return CounterListTile(
76 | key: Key('counter-${counter.id}'),
77 | counter: counter,
78 | onDecrement: _decrement,
79 | onIncrement: _increment,
80 | onDismissed: _delete,
81 | );
82 | },
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/lib/pages/streams_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:multiple_counters_flutter/common_widgets/counter_list_tile.dart';
5 | import 'package:multiple_counters_flutter/common_widgets/list_items_builder.dart';
6 | import 'package:multiple_counters_flutter/database.dart';
7 |
8 | class StreamsPage extends StatelessWidget {
9 | StreamsPage({this.database, this.stream});
10 | final Database database;
11 | final Stream> stream;
12 |
13 | void _createCounter() async {
14 | await database.createCounter();
15 | }
16 |
17 | void _increment(Counter counter) async {
18 | counter.value++;
19 | await database.setCounter(counter);
20 | }
21 |
22 | void _decrement(Counter counter) async {
23 | counter.value--;
24 | await database.setCounter(counter);
25 | }
26 |
27 | void _delete(Counter counter) async {
28 | await database.deleteCounter(counter);
29 | }
30 |
31 | @override
32 | Widget build(BuildContext context) {
33 | return Scaffold(
34 | appBar: AppBar(
35 | title: Text('Streams'),
36 | elevation: 1.0,
37 | ),
38 | body: Container(
39 | child: _buildContent(),
40 | ),
41 | floatingActionButton: FloatingActionButton(
42 | child: Icon(Icons.add),
43 | onPressed: _createCounter,
44 | ),
45 | );
46 | }
47 |
48 | Widget _buildContent() {
49 | return StreamBuilder>(
50 | stream: stream,
51 | builder: (context, snapshot) {
52 | return ListItemsBuilder(
53 | items: snapshot.hasData ? snapshot.data : null,
54 | itemBuilder: (context, counter) {
55 | return CounterListTile(
56 | key: Key('counter-${counter.id}'),
57 | counter: counter,
58 | onDecrement: _decrement,
59 | onIncrement: _increment,
60 | onDismissed: _delete,
61 | );
62 | },
63 | );
64 | },
65 | );
66 | }
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/multiple_counters_flutter.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/multiple_counters_flutter_android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | archive:
5 | dependency: transitive
6 | description:
7 | name: archive
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "2.0.11"
11 | args:
12 | dependency: transitive
13 | description:
14 | name: args
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "1.5.2"
18 | async:
19 | dependency: transitive
20 | description:
21 | name: async
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "2.4.0"
25 | boolean_selector:
26 | dependency: transitive
27 | description:
28 | name: boolean_selector
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "1.0.5"
32 | charcode:
33 | dependency: transitive
34 | description:
35 | name: charcode
36 | url: "https://pub.dartlang.org"
37 | source: hosted
38 | version: "1.1.2"
39 | cloud_firestore:
40 | dependency: "direct main"
41 | description:
42 | name: cloud_firestore
43 | url: "https://pub.dartlang.org"
44 | source: hosted
45 | version: "0.7.4"
46 | collection:
47 | dependency: transitive
48 | description:
49 | name: collection
50 | url: "https://pub.dartlang.org"
51 | source: hosted
52 | version: "1.14.11"
53 | convert:
54 | dependency: transitive
55 | description:
56 | name: convert
57 | url: "https://pub.dartlang.org"
58 | source: hosted
59 | version: "2.1.1"
60 | crypto:
61 | dependency: transitive
62 | description:
63 | name: crypto
64 | url: "https://pub.dartlang.org"
65 | source: hosted
66 | version: "2.1.3"
67 | cupertino_icons:
68 | dependency: "direct main"
69 | description:
70 | name: cupertino_icons
71 | url: "https://pub.dartlang.org"
72 | source: hosted
73 | version: "0.1.2"
74 | firebase_core:
75 | dependency: transitive
76 | description:
77 | name: firebase_core
78 | url: "https://pub.dartlang.org"
79 | source: hosted
80 | version: "0.2.4"
81 | firebase_database:
82 | dependency: "direct main"
83 | description:
84 | name: firebase_database
85 | url: "https://pub.dartlang.org"
86 | source: hosted
87 | version: "1.0.4"
88 | flutter:
89 | dependency: "direct main"
90 | description: flutter
91 | source: sdk
92 | version: "0.0.0"
93 | flutter_redux:
94 | dependency: "direct main"
95 | description:
96 | name: flutter_redux
97 | url: "https://pub.dartlang.org"
98 | source: hosted
99 | version: "0.5.2"
100 | flutter_test:
101 | dependency: "direct dev"
102 | description: flutter
103 | source: sdk
104 | version: "0.0.0"
105 | image:
106 | dependency: transitive
107 | description:
108 | name: image
109 | url: "https://pub.dartlang.org"
110 | source: hosted
111 | version: "2.1.4"
112 | matcher:
113 | dependency: transitive
114 | description:
115 | name: matcher
116 | url: "https://pub.dartlang.org"
117 | source: hosted
118 | version: "0.12.6"
119 | meta:
120 | dependency: transitive
121 | description:
122 | name: meta
123 | url: "https://pub.dartlang.org"
124 | source: hosted
125 | version: "1.1.8"
126 | path:
127 | dependency: transitive
128 | description:
129 | name: path
130 | url: "https://pub.dartlang.org"
131 | source: hosted
132 | version: "1.6.4"
133 | pedantic:
134 | dependency: transitive
135 | description:
136 | name: pedantic
137 | url: "https://pub.dartlang.org"
138 | source: hosted
139 | version: "1.8.0+1"
140 | petitparser:
141 | dependency: transitive
142 | description:
143 | name: petitparser
144 | url: "https://pub.dartlang.org"
145 | source: hosted
146 | version: "2.4.0"
147 | quiver:
148 | dependency: transitive
149 | description:
150 | name: quiver
151 | url: "https://pub.dartlang.org"
152 | source: hosted
153 | version: "2.0.5"
154 | redux:
155 | dependency: "direct main"
156 | description:
157 | name: redux
158 | url: "https://pub.dartlang.org"
159 | source: hosted
160 | version: "3.0.0"
161 | scoped_model:
162 | dependency: "direct main"
163 | description:
164 | name: scoped_model
165 | url: "https://pub.dartlang.org"
166 | source: hosted
167 | version: "0.3.0"
168 | sky_engine:
169 | dependency: transitive
170 | description: flutter
171 | source: sdk
172 | version: "0.0.99"
173 | source_span:
174 | dependency: transitive
175 | description:
176 | name: source_span
177 | url: "https://pub.dartlang.org"
178 | source: hosted
179 | version: "1.5.5"
180 | stack_trace:
181 | dependency: transitive
182 | description:
183 | name: stack_trace
184 | url: "https://pub.dartlang.org"
185 | source: hosted
186 | version: "1.9.3"
187 | stream_channel:
188 | dependency: transitive
189 | description:
190 | name: stream_channel
191 | url: "https://pub.dartlang.org"
192 | source: hosted
193 | version: "2.0.0"
194 | string_scanner:
195 | dependency: transitive
196 | description:
197 | name: string_scanner
198 | url: "https://pub.dartlang.org"
199 | source: hosted
200 | version: "1.0.5"
201 | term_glyph:
202 | dependency: transitive
203 | description:
204 | name: term_glyph
205 | url: "https://pub.dartlang.org"
206 | source: hosted
207 | version: "1.1.0"
208 | test_api:
209 | dependency: transitive
210 | description:
211 | name: test_api
212 | url: "https://pub.dartlang.org"
213 | source: hosted
214 | version: "0.2.11"
215 | typed_data:
216 | dependency: transitive
217 | description:
218 | name: typed_data
219 | url: "https://pub.dartlang.org"
220 | source: hosted
221 | version: "1.1.6"
222 | vector_math:
223 | dependency: transitive
224 | description:
225 | name: vector_math
226 | url: "https://pub.dartlang.org"
227 | source: hosted
228 | version: "2.0.8"
229 | xml:
230 | dependency: transitive
231 | description:
232 | name: xml
233 | url: "https://pub.dartlang.org"
234 | source: hosted
235 | version: "3.5.0"
236 | sdks:
237 | dart: ">=2.5.0 <3.0.0"
238 | flutter: ">=0.2.4 <2.0.0"
239 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: multiple_counters_flutter
2 | description: A new Flutter project.
3 |
4 | version: 1.0.0+1
5 |
6 | environment:
7 | sdk: ">=2.5.0 <3.0.0"
8 |
9 | dependencies:
10 | flutter:
11 | sdk: flutter
12 | firebase_database: any
13 | cloud_firestore: any
14 | scoped_model: any
15 | redux: any
16 | flutter_redux: any
17 |
18 | # The following adds the Cupertino Icons font to your application.
19 | # Use with the CupertinoIcons class for iOS style icons.
20 | cupertino_icons: ^0.1.2
21 |
22 | dev_dependencies:
23 | flutter_test:
24 | sdk: flutter
25 |
26 |
27 | # For information on the generic Dart part of this file, see the
28 | # following page: https://www.dartlang.org/tools/pub/pubspec
29 |
30 | # The following section is specific to Flutter.
31 | flutter:
32 |
33 | # The following line ensures that the Material Icons font is
34 | # included with your application, so that you can use the icons in
35 | # the material Icons class.
36 | uses-material-design: true
37 |
38 | # To add assets to your application, add an assets section, like this:
39 | # assets:
40 | # - images/a_dot_burr.jpeg
41 | # - images/a_dot_ham.jpeg
42 |
43 | # An image asset can refer to one or more resolution-specific "variants", see
44 | # https://flutter.io/assets-and-images/#resolution-aware.
45 |
46 | # For details regarding adding assets from package dependencies, see
47 | # https://flutter.io/assets-and-images/#from-packages
48 |
49 | # To add custom fonts to your application, add a fonts section here,
50 | # in this "flutter" section. Each entry in this list should have a
51 | # "family" key with the font family name, and a "fonts" key with a
52 | # list giving the asset and other descriptors for the font. For
53 | # example:
54 | # fonts:
55 | # - family: Schyler
56 | # fonts:
57 | # - asset: fonts/Schyler-Regular.ttf
58 | # - asset: fonts/Schyler-Italic.ttf
59 | # style: italic
60 | # - family: Trajan Pro
61 | # fonts:
62 | # - asset: fonts/TrajanPro.ttf
63 | # - asset: fonts/TrajanPro_Bold.ttf
64 | # weight: 700
65 | #
66 | # For details regarding fonts from package dependencies,
67 | # see https://flutter.io/custom-fonts/#from-packages
68 |
--------------------------------------------------------------------------------
/screenshots/multiple_counters_tabs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/screenshots/multiple_counters_tabs.png
--------------------------------------------------------------------------------
/screenshots/poster-state-management.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bizz84/multiple-counters-flutter/5451582527a6c62b2f033b238f82762ce4b5b6ad/screenshots/poster-state-management.png
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter
3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to
4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties
5 | // are correct.
6 |
7 | import 'package:flutter/material.dart';
8 | import 'package:flutter_test/flutter_test.dart';
9 |
10 | import 'package:multiple_counters_flutter/main.dart';
11 |
12 | void main() {
13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
14 | // Build our app and trigger a frame.
15 | await tester.pumpWidget(new MyApp());
16 |
17 | // Verify that our counter starts at 0.
18 | expect(find.text('0'), findsOneWidget);
19 | expect(find.text('1'), findsNothing);
20 |
21 | // Tap the '+' icon and trigger a frame.
22 | await tester.tap(find.byIcon(Icons.add));
23 | await tester.pump();
24 |
25 | // Verify that our counter has incremented.
26 | expect(find.text('0'), findsNothing);
27 | expect(find.text('1'), findsOneWidget);
28 | });
29 | }
30 |
--------------------------------------------------------------------------------