diff --git a/.circleci/config.yml b/.circleci/config.yml index 6adbb0c29..ccdca4bdf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ aliases: - &flutter_environment - image: cirrusci/flutter:stable - &node_environment - - image: circleci/node:10 + - image: circleci/node:16 - &restore_cache keys: # when lock file changes, use increasingly general patterns to restore cache @@ -66,58 +66,13 @@ jobs: key: pub-packages-v1-{{ checksum "packages/graphql/pubspec.yaml" }}-{{ checksum "packages/graphql_flutter/pubspec.yaml" }} paths: - ~/.pub-cache - lint: - docker: *flutter_environment - steps: - - checkout - - restore_cache: *restore_cache - - run: *install_yaml_processor - - run: - name: Code formating and analyzing (graphql) - command: | - cd packages/graphql - dartfmt **/*.dart -n --set-exit-if-changed - pub get - dart analyze lib - cd example && pub get && dart analyze - - run: - name: Code formating and analyzing (graphql_flutter) - command: | - cd packages/graphql_flutter - dartfmt **/*.dart -n --set-exit-if-changed - flutter analyze - coverage: - docker: *flutter_environment - steps: - - checkout - - restore_cache: *restore_cache - - run: *install_yaml_processor - - run: - name: Code coverage (graphql, vm only) - command: | - cd packages/graphql - pub get - pub global activate coverage - dart --enable_asserts --enable-vm-service test/coverage.dart - - run: - name: Code coverage (graphql_flutter) - command: | - cd packages/graphql_flutter - flutter test --coverage - - run: - name: Upload coverage - command: | - bash <(curl -s https://codecov.io/bash) -F graphql_flutter - bash <(curl -s https://codecov.io/bash) -F graphql_flutter -B `git rev-parse --abbrev-ref HEAD`_pseudo_branch_package_graphql_flutter - bash <(curl -s https://codecov.io/bash) -F graphql_client - bash <(curl -s https://codecov.io/bash) -F graphql_client -B `git rev-parse --abbrev-ref HEAD`_pseudo_branch_package_graphql_client release: docker: *node_environment steps: - checkout - run: name: Generate release tags - command: npx -p semantic-release -p @semantic-release/changelog -p @semantic-release/git semantic-release + command: nvm install 'lts/*' && npx -p semantic-release -p @semantic-release/changelog -p @semantic-release/git semantic-release sync_with_beta: docker: *node_environment steps: @@ -154,7 +109,7 @@ jobs: - run: name: Bump version numbers command: | - if [[ $CIRCLE_TAG =~ ^.*beta.*$ ]]; then BRANCH=beta; else BRANCH=master; fi + if [[ $CIRCLE_TAG =~ ^.*beta.*$ ]]; then BRANCH=beta; else BRANCH=beta; fi git checkout $BRANCH git add packages/graphql/pubspec.yaml packages/graphql_flutter/pubspec.yaml @@ -177,69 +132,12 @@ jobs: ~/yq -P -i eval '.dependencies.graphql=env(GRAPHQL_DEP) | del(.publish_to)' pubspec.yaml pub publish -f workflows: - nightly: - triggers: - - schedule: - cron: "0 12 * * *" - filters: - branches: - only: - - beta - jobs: - - dependencies - - lint: - requires: - - dependencies - - coverage: - requires: - - dependencies - - release: - requires: - - dependencies - - lint - - coverage - weekly: - triggers: - - schedule: - cron: "0 21 * * 1" - filters: - branches: - only: - - master - jobs: - - dependencies - - lint: - requires: - - dependencies - - coverage: - requires: - - dependencies - - release: - requires: - - dependencies - - lint - - coverage - - sync_with_beta: - requires: - - release default: jobs: - dependencies: filters: tags: only: /^.*$/ - - lint: - requires: - - dependencies - filters: - tags: - ignore: /^.*$/ - - coverage: - requires: - - dependencies - filters: - tags: - ignore: /^.*$/ - publish: requires: - dependencies diff --git a/.codecov.yml b/.codecov.yml index 26552ab59..7b1bff2a6 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,10 +1,3 @@ -flags: - - graphql_flutter: - paths: - - packages/graphql_flutter/ - - graphql_client: - paths: - - packages/graphql/ comment: require_changes: true # Only post a comment if coverage changes. @@ -17,5 +10,5 @@ coverage: # don't factor tests into coverage scores ignore: - - packages/graphql_flutter/test - - packages/graphql/test + - '**/example/' + - '**/*.g.dart' diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9cc2f8751..28b1c58af 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,12 +10,16 @@ assignees: '' **Describe the issue** A clear and concise description of what the problem is. -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error +**To Reproduce (MUST BE PROVIDED)** + +To help us to understand what is going on and what is the easy way to resolve this bug +we ask to describe the issue with a small code example, or if the problem is easy you can +describe the issue as follows: + +1. When I use '...' to try '...' +2. I receive the following '....' +4. See error also the error if any +3. But I would like to receive '....' **Expected behavior** diff --git a/.github/workflows/graphql_build.yml b/.github/workflows/graphql_build.yml new file mode 100644 index 000000000..58b12b68a --- /dev/null +++ b/.github/workflows/graphql_build.yml @@ -0,0 +1,25 @@ +name: graphql Dart Code sanity check + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1 + with: + sdk: stable + - name: Install dependencies + run: | + cd packages/graphql + dart pub get + - name: Code formatting check + run: | + cd packages/graphql + dart format --set-exit-if-changed . + - name: Code analyze check + run: | + cd packages/graphql + dart analyze lib + dart analyze test \ No newline at end of file diff --git a/.github/workflows/graphql_codcoverage.yml b/.github/workflows/graphql_codcoverage.yml new file mode 100644 index 000000000..6804606e4 --- /dev/null +++ b/.github/workflows/graphql_codcoverage.yml @@ -0,0 +1,24 @@ +name: graphql Code coverage + +on: [ push, pull_request ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1 + with: + sdk: stable + - name: Run tests with coverage + run: | + cd packages/graphql + dart pub get + dart run test --coverage="coverage" + dart run coverage:format_coverage --lcov --in=coverage --out=coverage.lcov --packages=.packages --report-on=lib + - name: Upload coverage file + run: | + curl -Os https://uploader.codecov.io/latest/linux/codecov + chmod +x codecov + ./codecov \ No newline at end of file diff --git a/.github/workflows/graphql_flutter_build.yml b/.github/workflows/graphql_flutter_build.yml new file mode 100644 index 000000000..68ca1ab6f --- /dev/null +++ b/.github/workflows/graphql_flutter_build.yml @@ -0,0 +1,22 @@ +name: graphql-flutter Dart Code sanity check + +on: [ push, pull_request ] + +jobs: + build: + # This job will run on ubuntu virtual machine + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: subosito/flutter-action@v1 + with: + channel: 'stable' + - run: | + cd packages/graphql_flutter + flutter pub get + - run: | + cd packages/graphql_flutter + flutter format --set-exit-if-changed . + - run: | + cd packages/graphql_flutter + flutter analyze . \ No newline at end of file diff --git a/.github/workflows/graphql_flutter_codcoverage.yml b/.github/workflows/graphql_flutter_codcoverage.yml new file mode 100644 index 000000000..d5a2bc914 --- /dev/null +++ b/.github/workflows/graphql_flutter_codcoverage.yml @@ -0,0 +1,21 @@ +name: graphql-flutter Code coverage + +on: [ push, pull_request ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: subosito/flutter-action@v1 + with: + channel: 'stable' + - run: | + cd packages/graphql_flutter + flutter pub get + flutter test --coverage + - name: Upload coverage file + run: | + curl -Os https://uploader.codecov.io/latest/linux/codecov + chmod +x codecov + ./codecov \ No newline at end of file diff --git a/.github/workflows/graphql_flutter_tests.yml b/.github/workflows/graphql_flutter_tests.yml new file mode 100644 index 000000000..5664413c6 --- /dev/null +++ b/.github/workflows/graphql_flutter_tests.yml @@ -0,0 +1,19 @@ +name: graphql-flutter Tests case + +on: [ push, pull_request ] + +jobs: + build: + # This job will run on ubuntu virtual machine + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: subosito/flutter-action@v1 + with: + channel: 'stable' + - run: | + cd packages/graphql_flutter + flutter pub get + - run: | + cd packages/graphql_flutter + flutter test . \ No newline at end of file diff --git a/.github/workflows/graphql_tests.yml b/.github/workflows/graphql_tests.yml new file mode 100644 index 000000000..cf4bbc895 --- /dev/null +++ b/.github/workflows/graphql_tests.yml @@ -0,0 +1,20 @@ +name: graphql Test case + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1 + with: + sdk: stable + - name: Install dependencies + run: | + cd packages/graphql + dart pub get + - name: Code formatting check + run: | + cd packages/graphql + dart test . \ No newline at end of file diff --git a/README.md b/README.md index 803f0087f..c00b42a40 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,6 @@ # GraphQL Flutter ### 📌   Bulletin -* See the [`v3 -> v4` Migration Guide](./changelog-v3-v4.md) if you're still on `v3`. -* [Maintenance status: Low](https://github.com/zino-app/graphql-flutter/issues/763). - * Follow [#762](https://github.com/zino-app/graphql-flutter/issues/762) for updates on the planned architecture walk through videos. * [Join the discord.][discord-link] ## About this project @@ -39,7 +36,13 @@ This is a Monorepo which contains the following packages: Here are some examples you can follow: 1. [Starwars Example](./examples/starwars) -2. [`flutter_bloc` example](./examples/flutter_bloc) + +## Utils Tools + +Around graphql_flutter are builds awesome tools like: + +1. [graphql_flutter_bloc](https://github.com/artflutter/graphql_flutter_bloc) +2. [graphql_codegen](https://github.com/heftapp/graphql_codegen) ## Articles and Videos @@ -121,10 +124,10 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind are welcome! -[build-status-badge]: https://img.shields.io/circleci/build/github/zino-app/graphql-flutter.svg?style=flat-square -[build-status-link]: https://circleci.com/gh/zino-app/graphql-flutter -[coverage-badge]: https://img.shields.io/codecov/c/github/zino-app/graphql-flutter.svg?style=flat-square -[coverage-link]: https://codecov.io/gh/zino-app/graphql-flutter +[build-status-badge]: https://img.shields.io/github/workflow/status/zino-hofmann/graphql-flutter/graphql-flutter%20Tests%20case?style=flat-square +[build-status-link]: https://github.com/zino-hofmann/graphql-flutter/actions +[coverage-badge]: https://img.shields.io/codecov/c/github/zino-hofmann/graphql-flutter/beta?style=flat-square +[coverage-link]: https://app.codecov.io/gh/zino-hofmann/graphql-flutter [version-badge]: https://img.shields.io/pub/v/graphql_flutter.svg?style=flat-square [package-link]: https://pub.dartlang.org/packages/graphql_flutter [package-link-client]: https://pub.dartlang.org/packages/graphql diff --git a/examples/flutter_bloc/.gitignore b/examples/flutter_bloc/.gitignore deleted file mode 100644 index 01e95b7cc..000000000 --- a/examples/flutter_bloc/.gitignore +++ /dev/null @@ -1,75 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.packages -.pub-cache/ -.pub/ -/build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages - -/ios/Flutter/Flutter.podspec -/ios/Flutter/flutter_export_environment.sh diff --git a/examples/flutter_bloc/.metadata b/examples/flutter_bloc/.metadata deleted file mode 100644 index 3b87eb506..000000000 --- a/examples/flutter_bloc/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: b712a172f9694745f50505c93340883493b505e5 - channel: stable - -project_type: app diff --git a/examples/flutter_bloc/README.md b/examples/flutter_bloc/README.md deleted file mode 100644 index ef6333997..000000000 --- a/examples/flutter_bloc/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# An example using graphql client with "flutter_bloc" - -This example uses [`flutter_bloc`](https://pub.dev/packages/flutter_bloc) package for state management and [`graphql client`](https://pub.dev/packages/graphql) to connect to GitHubs' GraphQL API to fetch and star/un-star your repositories. - -## Running this example - -Before running this example, make sure to create a `local.dart` file inside the `lib` directory, and add your Github token, as shown below: - -```dart -const String YOUR_PERSONAL_ACCESS_TOKEN = - ''; -``` diff --git a/examples/flutter_bloc/android/app/build.gradle b/examples/flutter_bloc/android/app/build.gradle deleted file mode 100644 index 0bb6f7ee6..000000000 --- a/examples/flutter_bloc/android/app/build.gradle +++ /dev/null @@ -1,61 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - compileSdkVersion 28 - - lintOptions { - disable 'InvalidPackage' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.graphql_flutter_bloc_example" - minSdkVersion 16 - targetSdkVersion 28 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' -} diff --git a/examples/flutter_bloc/android/app/src/main/AndroidManifest.xml b/examples/flutter_bloc/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index eeea35399..000000000 --- a/examples/flutter_bloc/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/flutter_bloc/android/app/src/main/java/com/example/graphql_flutter_bloc_example/MainActivity.java b/examples/flutter_bloc/android/app/src/main/java/com/example/graphql_flutter_bloc_example/MainActivity.java deleted file mode 100644 index 4b7607f37..000000000 --- a/examples/flutter_bloc/android/app/src/main/java/com/example/graphql_flutter_bloc_example/MainActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.graphql_flutter_bloc_example; - -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; - -public class MainActivity extends FlutterActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); - } -} diff --git a/examples/flutter_bloc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/flutter_bloc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7..000000000 Binary files a/examples/flutter_bloc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/examples/flutter_bloc/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/flutter_bloc/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79b..000000000 Binary files a/examples/flutter_bloc/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/examples/flutter_bloc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/flutter_bloc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 09d439148..000000000 Binary files a/examples/flutter_bloc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/examples/flutter_bloc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/flutter_bloc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34..000000000 Binary files a/examples/flutter_bloc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/examples/flutter_bloc/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/flutter_bloc/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eeb..000000000 Binary files a/examples/flutter_bloc/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/examples/flutter_bloc/android/app/src/main/res/values/styles.xml b/examples/flutter_bloc/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 00fa4417c..000000000 --- a/examples/flutter_bloc/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/examples/flutter_bloc/android/build.gradle b/examples/flutter_bloc/android/build.gradle deleted file mode 100644 index bb8a30389..000000000 --- a/examples/flutter_bloc/android/build.gradle +++ /dev/null @@ -1,29 +0,0 @@ -buildscript { - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' - } -} - -allprojects { - repositories { - google() - jcenter() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/examples/flutter_bloc/android/gradle.properties b/examples/flutter_bloc/android/gradle.properties deleted file mode 100644 index 2bd6f4fda..000000000 --- a/examples/flutter_bloc/android/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M - diff --git a/examples/flutter_bloc/android/gradle/wrapper/gradle-wrapper.properties b/examples/flutter_bloc/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 2819f022f..000000000 --- a/examples/flutter_bloc/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Jun 23 08:50:38 CEST 2017 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/examples/flutter_bloc/android/settings.gradle b/examples/flutter_bloc/android/settings.gradle deleted file mode 100644 index 5a2f14fb1..000000000 --- a/examples/flutter_bloc/android/settings.gradle +++ /dev/null @@ -1,15 +0,0 @@ -include ':app' - -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() - -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } -} - -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory -} diff --git a/examples/flutter_bloc/ios/Flutter/AppFrameworkInfo.plist b/examples/flutter_bloc/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 6b4c0f78a..000000000 --- a/examples/flutter_bloc/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 8.0 - - diff --git a/examples/flutter_bloc/ios/Flutter/Debug.xcconfig b/examples/flutter_bloc/ios/Flutter/Debug.xcconfig deleted file mode 100644 index e8efba114..000000000 --- a/examples/flutter_bloc/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" diff --git a/examples/flutter_bloc/ios/Flutter/Release.xcconfig b/examples/flutter_bloc/ios/Flutter/Release.xcconfig deleted file mode 100644 index 399e9340e..000000000 --- a/examples/flutter_bloc/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" diff --git a/examples/flutter_bloc/ios/Podfile b/examples/flutter_bloc/ios/Podfile deleted file mode 100644 index f7d6a5e68..000000000 --- a/examples/flutter_bloc/ios/Podfile +++ /dev/null @@ -1,38 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '9.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/examples/flutter_bloc/ios/Runner.xcodeproj/project.pbxproj b/examples/flutter_bloc/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index e9bd931ce..000000000 --- a/examples/flutter_bloc/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,567 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 1B57E325128DB676C4FB7095 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FCD2C1A34A03613207476C4 /* libPods-Runner.a */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 5CA22CE3629E2B7CB9BEEEFE /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 18DF0D89439134222F2FF830 /* libPods-Runner.a */; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; - 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 18DF0D89439134222F2FF830 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 40996C314C6E9E0B16D16A81 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 58CBAC75E2382AB59FB2BDD3 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 6933C3904819825D210B7737 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C9E86049F302A5727FC2A707 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 5CA22CE3629E2B7CB9BEEEFE /* libPods-Runner.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 34BDF72114FCEEFDE8EE0D84 /* Pods */ = { - isa = PBXGroup; - children = ( - 6933C3904819825D210B7737 /* Pods-Runner.debug.xcconfig */, - 40996C314C6E9E0B16D16A81 /* Pods-Runner.release.xcconfig */, - 58CBAC75E2382AB59FB2BDD3 /* Pods-Runner.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; - 72D400AF5A9CD38CB2CFC008 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 18DF0D89439134222F2FF830 /* libPods-Runner.a */, - ); - name = Frameworks; - sourceTree = ""; - }; - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - 34BDF72114FCEEFDE8EE0D84 /* Pods */, - 72D400AF5A9CD38CB2CFC008 /* Frameworks */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - ); - path = Runner; - sourceTree = ""; - }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 97C146F21CF9000F007C117D /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 5BD8D1480982C5095378BE4D /* [CP] Check Pods Manifest.lock */, - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - E0273651DDFDE1C239C56D6C /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1020; - ORGANIZATIONNAME = "The Chromium Authors"; - TargetAttributes = { - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 5BD8D1480982C5095378BE4D /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - 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"; - showEnvVarsInLog = 0; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; - E0273651DDFDE1C239C56D6C /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../Flutter/Flutter.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, - 97C146F31CF9000F007C117D /* main.m in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = S8QB4VV633; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.graphqlFlutterBlocExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.graphqlFlutterBlocExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.graphqlFlutterBlocExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/examples/flutter_bloc/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/flutter_bloc/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a16e..000000000 --- a/examples/flutter_bloc/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/examples/flutter_bloc/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/flutter_bloc/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index a28140cfd..000000000 --- a/examples/flutter_bloc/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/flutter_bloc/ios/Runner.xcworkspace/contents.xcworkspacedata b/examples/flutter_bloc/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc14c..000000000 --- a/examples/flutter_bloc/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/examples/flutter_bloc/ios/Runner/AppDelegate.h b/examples/flutter_bloc/ios/Runner/AppDelegate.h deleted file mode 100644 index 36e21bbf9..000000000 --- a/examples/flutter_bloc/ios/Runner/AppDelegate.h +++ /dev/null @@ -1,6 +0,0 @@ -#import -#import - -@interface AppDelegate : FlutterAppDelegate - -@end diff --git a/examples/flutter_bloc/ios/Runner/AppDelegate.m b/examples/flutter_bloc/ios/Runner/AppDelegate.m deleted file mode 100644 index 59a72e90b..000000000 --- a/examples/flutter_bloc/ios/Runner/AppDelegate.m +++ /dev/null @@ -1,13 +0,0 @@ -#include "AppDelegate.h" -#include "GeneratedPluginRegistrant.h" - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application - didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - // Override point for customization after application launch. - return [super application:application didFinishLaunchingWithOptions:launchOptions]; -} - -@end diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fab2..000000000 --- a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index 3d43d11e6..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 28c6bf030..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 2ccbfd967..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b0b..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cde12118..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e7e..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index dcdc2306c..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2ccbfd967..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8f5..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b8609..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b8609..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d164a..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d39..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 6a84f41e1..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index d0e1f5853..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2fd..000000000 --- a/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ diff --git a/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725b7..000000000 --- a/examples/flutter_bloc/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/examples/flutter_bloc/ios/Runner/Base.lproj/LaunchScreen.storyboard b/examples/flutter_bloc/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c7c..000000000 --- a/examples/flutter_bloc/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/flutter_bloc/ios/Runner/Base.lproj/Main.storyboard b/examples/flutter_bloc/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c28516f..000000000 --- a/examples/flutter_bloc/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/flutter_bloc/ios/Runner/Info.plist b/examples/flutter_bloc/ios/Runner/Info.plist deleted file mode 100644 index d510f35a3..000000000 --- a/examples/flutter_bloc/ios/Runner/Info.plist +++ /dev/null @@ -1,45 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - graphql_flutter_bloc_example - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - - diff --git a/examples/flutter_bloc/ios/Runner/main.m b/examples/flutter_bloc/ios/Runner/main.m deleted file mode 100644 index dff6597e4..000000000 --- a/examples/flutter_bloc/ios/Runner/main.m +++ /dev/null @@ -1,9 +0,0 @@ -#import -#import -#import "AppDelegate.h" - -int main(int argc, char* argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/examples/flutter_bloc/lib/bloc.dart b/examples/flutter_bloc/lib/bloc.dart deleted file mode 100644 index f608497b3..000000000 --- a/examples/flutter_bloc/lib/bloc.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'blocs/repos/events.dart'; -import 'blocs/repos/models.dart'; -import 'blocs/repos/my_repos_bloc.dart'; -import 'blocs/repos/states.dart'; - -class BlocPage extends StatefulWidget { - @override - _BlocPageState createState() => _BlocPageState(); -} - -class _BlocPageState extends State { - @override - void initState() { - super.initState(); - BlocProvider.of(context).add(LoadMyRepos(numOfReposToLoad: 50)); - } - - @override - void dispose() { - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Flutter Bloc GraphQL Example"), - ), - body: Container( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - TextField( - decoration: const InputDecoration( - labelText: 'Number of repositories (default 50)', - ), - keyboardType: TextInputType.number, - textAlign: TextAlign.center, - onChanged: (String n) { - BlocProvider.of(context).add(LoadMyRepos(numOfReposToLoad: int.parse(n) ?? 50)); - }, - ), - SizedBox( - height: 10, - ), - new LoadRepositories( - bloc: BlocProvider.of(context), - ) - ], - ), - ), - ); - } -} - -class LoadRepositories extends StatelessWidget { - final MyGithubReposBloc bloc; - - const LoadRepositories({Key key, this.bloc}) : super(key: key); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - cubit: bloc, - builder: (BuildContext context, MyGithubReposState state) { - if (state is ReposLoading) { - return Expanded( - child: Container( - child: Center( - child: CircularProgressIndicator( - semanticsLabel: "Loading ...", - ), - ), - ), - ); - } - - if (state is ReposNotLoaded) { - return Text("${state.errors}"); - } - - if (state is ReposLoaded) { - final List repositories = state.results; - - return Expanded( - child: ListView.builder( - itemCount: state.results.length, - itemBuilder: (BuildContext context, int index) => - StarrableRepository( - repository: repositories[index], - reposBloc: bloc, - ), - ), - ); - } - - return Text(null); - }, - ); - } -} - -class StarrableRepository extends StatelessWidget { - const StarrableRepository({ - Key key, - @required this.repository, - @required this.reposBloc, - }) : assert(reposBloc != null), - assert(repository != null), - super(key: key); - - final Repo repository; - final MyGithubReposBloc reposBloc; - - @override - Widget build(BuildContext context) { - return ListTile( - leading: _isRepoStarred(), - trailing: _showLoadingIndicator(), - title: Text(repository.name), - onTap: () { - reposBloc.add(MutateToggleStar(repo: repository)); - }, - ); - } - - Widget _showLoadingIndicator() { - if (repository.isLoading) { - return CircularProgressIndicator(); - } else { - return null; - } - } - - Widget _isRepoStarred() { - if (repository.viewerHasStarred) { - return Icon( - Icons.star, - color: Colors.amber, - ); - } else { - return Icon(Icons.star_border); - } - } -} diff --git a/examples/flutter_bloc/lib/blocs/repos/events.dart b/examples/flutter_bloc/lib/blocs/repos/events.dart deleted file mode 100644 index d3369da0e..000000000 --- a/examples/flutter_bloc/lib/blocs/repos/events.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; - -import 'models.dart'; - -@immutable -abstract class MyGithubReposEvent extends Equatable { - MyGithubReposEvent([List props = const []]) : super(props); -} - -// load your repositories -class LoadMyRepos extends MyGithubReposEvent { - // the number of repositories to load, default is 50 - final int numOfReposToLoad; - - LoadMyRepos({this.numOfReposToLoad: 50}) : super([numOfReposToLoad]); - - @override - String toString() => 'LoadMyRepos'; -} - -// update a repo with new mutation status -class MutateToggleStar extends MyGithubReposEvent { - // the number of repositories to load, default is 50 - final Repo repo; - - MutateToggleStar({this.repo}) : super([repo]); - - @override - String toString() => 'MutateToggleStar'; -} - -// update a repo with new mutation status -// class UpdateReposAfterMutations extends MyGithubReposEvent { -// // the number of repositories to load, default is 50 -// final Repo repo; - -// UpdateReposAfterMutations({this.repo}) : super([repo]); - -// @override -// String toString() => 'UpdateReposAfterMutations'; -// } diff --git a/examples/flutter_bloc/lib/blocs/repos/models.dart b/examples/flutter_bloc/lib/blocs/repos/models.dart deleted file mode 100644 index 1a40d8005..000000000 --- a/examples/flutter_bloc/lib/blocs/repos/models.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Repo { - const Repo({ - this.id, - this.name, - this.viewerHasStarred, - this.isLoading: false, - }); - - final String id; - final String name; - final bool viewerHasStarred; - final bool isLoading; -} diff --git a/examples/flutter_bloc/lib/blocs/repos/my_repos_bloc.dart b/examples/flutter_bloc/lib/blocs/repos/my_repos_bloc.dart deleted file mode 100644 index 239b3dc80..000000000 --- a/examples/flutter_bloc/lib/blocs/repos/my_repos_bloc.dart +++ /dev/null @@ -1,135 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/widgets.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:graphql_flutter_bloc_example/blocs/repos/events.dart'; -import 'package:graphql_flutter_bloc_example/blocs/repos/models.dart'; -import 'package:graphql_flutter_bloc_example/blocs/repos/states.dart'; -import 'package:graphql_flutter_bloc_example/repository.dart'; - -class MyGithubReposBloc extends Bloc { - final GithubRepository githubRepository; - - // this a bit of a hack - List githubRepositories; - - MyGithubReposBloc({@required this.githubRepository}) : super(ReposLoading()); - - @override - Stream mapEventToState( - MyGithubReposEvent event, - ) async* { - try { - if (event is LoadMyRepos) { - yield* _mapReposToState(event.numOfReposToLoad); - // } else if (event is UpdateReposAfterMutations) { - // yield* _mapUpdateAfterMutatationToState(event.repo); - } else if (event is MutateToggleStar) { - yield* _mapMutateStarRepositoryToState(event.repo); - } - } catch (_, stackTrace) { - print('$_ $stackTrace'); - yield state; - } - } - - Stream _mapReposToState(int numOfRepositories) async* { - try { - yield ReposLoading(); - - final queryResults = - await this.githubRepository.getRepositories(numOfRepositories); - - if (queryResults.hasException) { - yield ReposNotLoaded(queryResults.exception.graphqlErrors); - return; - } - - final List repos = - queryResults.data['viewer']['repositories']['nodes'] as List; - - final List listOfRepos = repos - .map((dynamic e) => Repo( - id: e['id'] as String, - name: e['name'] as String, - viewerHasStarred: e['viewerHasStarred'] as bool, - )) - .toList(); - - githubRepositories = listOfRepos; - - // pass the data instead - yield ReposLoaded(results: listOfRepos); - } catch (error) { - yield ReposNotLoaded(error); - } - } - - // Stream _mapUpdateAfterMutatationToState( - // Repo repo) async* { - // try { - // // assert(githubRepositories != null); - - // var repos = githubRepositories - // .map((Repo r) => repo.id == r.id ? repo : r) - // .toList(); - // yield ReposLoaded(repos); - // } catch (error) { - // yield ReposNotLoaded(error); - // } - // } - - Stream _mapMutateStarRepositoryToState(Repo repo) async* { - try { - final loadingRepo = Repo( - id: repo.id, - name: repo.name, - viewerHasStarred: repo.viewerHasStarred, - isLoading: true, - ); - - // mark repo as loading - githubRepositories = githubRepositories - .map((Repo r) => repo.id == r.id ? loadingRepo : r) - .toList(); - - // pass the data instead - yield ReposLoaded(results: githubRepositories); - - final queryResults = await githubRepository.toggleRepoStar(repo); - - if (queryResults.hasException) { - // @TODO Improve error handling here, may be introduce a hasError Method - yield ReposNotLoaded(queryResults.exception.graphqlErrors); - return; - } - - var mutatedRepo = extractRepositoryData(queryResults.data); - - final notloadingRepo = Repo( - id: repo.id, - name: repo.name, - viewerHasStarred: mutatedRepo['viewerHasStarred'], - isLoading: false, - ); - - githubRepositories = githubRepositories - .map((Repo r) => repo.id == r.id ? notloadingRepo : r) - .toList(); - - yield ReposLoaded(results: githubRepositories); - } catch (error) { - yield ReposNotLoaded(error); - } - } - - Map extractRepositoryData(Map data) { - final Map action = data['action'] as Map; - - if (action == null) { - return null; - } - - return action['starrable'] as Map; - } -} diff --git a/examples/flutter_bloc/lib/blocs/repos/states.dart b/examples/flutter_bloc/lib/blocs/repos/states.dart deleted file mode 100644 index 10062d7f2..000000000 --- a/examples/flutter_bloc/lib/blocs/repos/states.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:graphql/client.dart'; -import 'package:graphql_flutter_bloc_example/blocs/repos/models.dart'; -import 'package:meta/meta.dart'; - -@immutable -abstract class MyGithubReposState extends Equatable { - MyGithubReposState([List props = const []]) : super(props); -} - -class ReposLoading extends MyGithubReposState { - @override - String toString() => 'ReposLoading'; -} - -class ReposLoaded extends MyGithubReposState { - final List results; - - ReposLoaded({@required this.results}) - : assert(results != null), - super([results]); - - @override - String toString() => 'ReposLoaded: { Github Repositories: $results }'; -} - -class ReposNotLoaded extends MyGithubReposState { - final List errors; - - ReposNotLoaded([this.errors]) : super([errors]); - - @override - String toString() => 'ReposNotLoaded'; -} - -// class ReposStarToggled extends MyGithubReposState { -// final QueryResult results; - -// ReposStarToggled([this.results]) : super([results]); - -// @override -// String toString() => 'ReposStarToggled'; -// } - -// class ReposStarNotToggled extends MyGithubReposState { -// @override -// String toString() => 'ReposStarNotToggled'; -// } diff --git a/examples/flutter_bloc/lib/extended_bloc.dart b/examples/flutter_bloc/lib/extended_bloc.dart deleted file mode 100644 index 7e850fb07..000000000 --- a/examples/flutter_bloc/lib/extended_bloc.dart +++ /dev/null @@ -1,126 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:graphql/client.dart'; -import 'package:graphql_flutter_bloc/graphql_flutter_bloc.dart'; - -import 'package:graphql_flutter_bloc_example/extended_bloc/repositories_bloc.dart'; - -class ExtendedBloc extends StatefulWidget { - @override - _ExtendedBlocState createState() => _ExtendedBlocState(); -} - -class _ExtendedBlocState extends State { - Completer _refreshCompleter; - RepositoriesBloc bloc; - - @override - void initState() { - super.initState(); - _refreshCompleter = Completer(); - bloc = BlocProvider.of(context)..run(); - } - - Future _handleRefreshStart(RepositoriesBloc bloc) { - bloc.refetch(); - return _refreshCompleter.future; - } - - @override - void dispose() { - bloc.dispose(); - super.dispose(); - } - - void _handleRefreshEnd() { - _refreshCompleter?.complete(); - _refreshCompleter = Completer(); - } - - Widget _displayResults(Map data, QueryResult result) { - final itemCount = data['viewer']['repositories']['nodes'].length; - - if (itemCount == 0) { - return ListView(children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.inbox), - SizedBox(width: 8), - Text('No data'), - ], - ) - ]); - } else { - return ListView.separated( - separatorBuilder: (_, __) => SizedBox( - height: 8.0, - ), - key: PageStorageKey('reports'), - itemCount: itemCount, - itemBuilder: (BuildContext context, int index) { - final pageInfo = data['viewer']['repositories']['pageInfo']; - - if (bloc.shouldFetchMore(index, 1)) { - bloc.fetchMore(after: pageInfo['endCursor']); - } - - final node = data['viewer']['repositories']['nodes'][index]; - - Widget tile = ListTile( - title: Text(node['name']), - ); - - if (bloc.isFetchingMore && index == itemCount - 1) { - tile = Column( - children: [ - tile, - Padding( - padding: const EdgeInsets.all(16.0), - child: CircularProgressIndicator(), - ), - ], - ); - } - - return tile; - }, - ); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Extended BLOC example'), - ), - body: RefreshIndicator( - onRefresh: () async => _handleRefreshStart(bloc), - child: BlocBuilder>>( - cubit: bloc, - builder: (_, state) { - if (state is! QueryStateRefetch) { - _handleRefreshEnd(); - } - - return state.when( - initial: () => Container(), - loading: (_) => Center(child: CircularProgressIndicator()), - error: (_, __) => ListView(children: [ - Text( - bloc.getError, - style: TextStyle(color: Theme.of(context).errorColor), - ) - ]), - loaded: _displayResults, - refetch: _displayResults, - fetchMore: _displayResults, - ); - }), - ), - ); - } -} diff --git a/examples/flutter_bloc/lib/extended_bloc/repositories_bloc.dart b/examples/flutter_bloc/lib/extended_bloc/repositories_bloc.dart deleted file mode 100644 index e9030cbee..000000000 --- a/examples/flutter_bloc/lib/extended_bloc/repositories_bloc.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:gql/language.dart'; -import 'package:graphql/client.dart'; -import 'package:graphql_flutter_bloc/graphql_flutter_bloc.dart'; - -class RepositoriesBloc extends QueryBloc> { - static int defaultLimit = 5; - - RepositoriesBloc({GraphQLClient client, WatchQueryOptions options}) - : super( - client: client, - options: options ?? - WatchQueryOptions( - document: parseString(r''' - query ReadRepositories($nRepositories: Int!, $after: String) { - viewer { - id - __typename - repositories(first: $nRepositories, after: $after) { - pageInfo { - endCursor - hasNextPage - } - nodes { - __typename - id - name - viewerHasStarred - } - } - } - } - '''), - variables: { - 'nRepositories': defaultLimit, - 'after': null, - 'affiliations': [ - 'OWNER', - 'ORGANIZATION_MEMBER', - 'COLLABORATOR' - ], - 'ownerAffiliations': [ - 'OWNER', - 'ORGANIZATION_MEMBER', - 'COLLABORATOR' - ] - }, - ), - ); - - @override - Map parseData(Map data) { - return data; - } - - @override - bool shouldFetchMore(int i, int threshold) { - return state.maybeWhen( - loaded: (data, result) { - return data['viewer']['repositories']['nodes'].length % - RepositoriesBloc.defaultLimit == - 0 && - i == data['viewer']['repositories']['nodes'].length - threshold; - }, - orElse: () => false); - } - - void fetchMore({String after}) { - add(QueryEvent.fetchMore( - options: FetchMoreOptions( - variables: {'nRepositories': 5, 'after': after}, - updateQuery: (dynamic previousResultData, dynamic fetchMoreResultData) { - final List repos = [ - ...previousResultData['viewer']['repositories']['nodes'] - as List, - ...fetchMoreResultData['viewer']['repositories']['nodes'] - as List - ]; - - fetchMoreResultData['viewer']['repositories']['nodes'] = repos; - - return fetchMoreResultData; - }, - ))); - } -} diff --git a/examples/flutter_bloc/lib/graphql_operation/mutations/addStar.dart b/examples/flutter_bloc/lib/graphql_operation/mutations/addStar.dart deleted file mode 100644 index aabb79004..000000000 --- a/examples/flutter_bloc/lib/graphql_operation/mutations/addStar.dart +++ /dev/null @@ -1,9 +0,0 @@ -const String addStar = r''' - mutation AddStar($starrableId: ID!) { - action: addStar(input: {starrableId: $starrableId}) { - starrable { - viewerHasStarred - } - } - } -'''; diff --git a/examples/flutter_bloc/lib/graphql_operation/mutations/mutations.dart b/examples/flutter_bloc/lib/graphql_operation/mutations/mutations.dart deleted file mode 100644 index 19ec2b51e..000000000 --- a/examples/flutter_bloc/lib/graphql_operation/mutations/mutations.dart +++ /dev/null @@ -1,2 +0,0 @@ -export './addStar.dart'; -export './removeStar.dart'; diff --git a/examples/flutter_bloc/lib/graphql_operation/mutations/removeStar.dart b/examples/flutter_bloc/lib/graphql_operation/mutations/removeStar.dart deleted file mode 100644 index 69f33295b..000000000 --- a/examples/flutter_bloc/lib/graphql_operation/mutations/removeStar.dart +++ /dev/null @@ -1,9 +0,0 @@ -const String removeStar = r''' - mutation RemoveStar($starrableId: ID!) { - action: removeStar(input: {starrableId: $starrableId}) { - starrable { - viewerHasStarred - } - } - } -'''; diff --git a/examples/flutter_bloc/lib/graphql_operation/queries/readRepositories.dart b/examples/flutter_bloc/lib/graphql_operation/queries/readRepositories.dart deleted file mode 100644 index 8eceea119..000000000 --- a/examples/flutter_bloc/lib/graphql_operation/queries/readRepositories.dart +++ /dev/null @@ -1,23 +0,0 @@ -const String readRepositories = r''' - query ReadRepositories($nRepositories: Int!) { - viewer { - repositories(last: $nRepositories) { - nodes { - __typename - id - name - viewerHasStarred - } - } - } - } -'''; - -const String testSubscription = r''' - subscription test { - deviceChanged(id: 2) { - id - name - } - } -'''; diff --git a/examples/flutter_bloc/lib/hive_init.dart b/examples/flutter_bloc/lib/hive_init.dart deleted file mode 100644 index 0d99fcf48..000000000 --- a/examples/flutter_bloc/lib/hive_init.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:flutter/foundation.dart' show kIsWeb; - -import 'package:hive/hive.dart' show Hive; -import 'package:path_provider/path_provider.dart' - show getApplicationDocumentsDirectory; -import 'package:path/path.dart' show join; - -import 'package:graphql/client.dart' show HiveStore; - -/// Initializes Hive with the path from [getApplicationDocumentsDirectory]. -/// -/// You can provide a [subDir] where the boxes should be stored. -/// -/// Extracted from [`hive_flutter` source][github] -/// -/// [github]: https://github.com/hivedb/hive/blob/5bf355496650017409fef4e9905e8826c5dc5bf3/hive_flutter/lib/src/hive_extensions.dart -Future initHiveForFlutter({ - String subDir, - Iterable boxes = const [HiveStore.defaultBoxName], -}) async { - if (!kIsWeb) { - var appDir = await getApplicationDocumentsDirectory(); - var path = appDir.path; - if (subDir != null) { - path = join(path, subDir); - } - Hive.init(path); - } - - for (var box in boxes) { - await Hive.openBox(box); - } -} diff --git a/examples/flutter_bloc/lib/local.dart b/examples/flutter_bloc/lib/local.dart deleted file mode 100644 index 47843adac..000000000 --- a/examples/flutter_bloc/lib/local.dart +++ /dev/null @@ -1,2 +0,0 @@ -// to run the example, replace with your GitHub token -const String YOUR_PERSONAL_ACCESS_TOKEN = ''; diff --git a/examples/flutter_bloc/lib/main.dart b/examples/flutter_bloc/lib/main.dart deleted file mode 100644 index f28229f7a..000000000 --- a/examples/flutter_bloc/lib/main.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:graphql/client.dart'; - -import 'package:graphql_flutter_bloc_example/bloc.dart'; -import 'package:graphql_flutter_bloc_example/hive_init.dart'; -import 'package:graphql_flutter_bloc_example/repository.dart'; -import 'package:graphql_flutter_bloc_example/blocs/repos/my_repos_bloc.dart'; -import 'package:graphql_flutter_bloc_example/extended_bloc/repositories_bloc.dart'; -import 'package:graphql_flutter_bloc_example/extended_bloc.dart'; - -// to run the example, replace with your GitHub token in ./local.dart -import './local.dart'; - -Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - await initHiveForFlutter(); - - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - routes: { - 'bloc': (_) => BlocProvider( - create: (context) => MyGithubReposBloc( - githubRepository: GithubRepository( - client: _client(), - ), - ), - child: BlocPage(), - ), - 'extended-bloc': (_) => BlocProvider( - create: (context) => RepositoriesBloc( - client: _client(), - ), - child: ExtendedBloc(), - ) - }, - home: Home(), - ); - } - - GraphQLClient _client() { - final HttpLink _httpLink = HttpLink( - 'https://api.github.com/graphql', - ); - - final AuthLink _authLink = AuthLink( - getToken: () => 'Bearer $YOUR_PERSONAL_ACCESS_TOKEN', - ); - - final Link _link = _authLink.concat(_httpLink); - - return GraphQLClient( - cache: GraphQLCache( - store: HiveStore(), - ), - link: _link, - ); - } -} - -class Home extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Select example"), - ), - body: ListView( - children: [ - ListTile( - title: Text('BLOC example'), - onTap: () => Navigator.of(context).pushNamed('bloc'), - ), - Divider(), - ListTile( - title: Text('Extended BLOC example'), - onTap: () => Navigator.of(context).pushNamed('extended-bloc'), - ), - Divider(), - ], - ), - ); - } -} diff --git a/examples/flutter_bloc/lib/repository.dart b/examples/flutter_bloc/lib/repository.dart deleted file mode 100644 index 6ffb25d88..000000000 --- a/examples/flutter_bloc/lib/repository.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/widgets.dart'; -import 'package:gql/language.dart'; -import 'package:graphql/client.dart'; -import 'package:graphql_flutter_bloc_example/blocs/repos/models.dart'; -import 'package:graphql_flutter_bloc_example/graphql_operation/mutations/mutations.dart' - as mutations; -import 'package:graphql_flutter_bloc_example/graphql_operation/queries/readRepositories.dart' - as queries; - -class GithubRepository { - final GraphQLClient client; - - GithubRepository({@required this.client}) : assert(client != null); - - Future getRepositories(int numOfRepositories) async { - final WatchQueryOptions _options = WatchQueryOptions( - document: parseString(queries.readRepositories), - variables: { - 'nRepositories': numOfRepositories, - }, - pollInterval: Duration(seconds: 4), - fetchResults: true, - ); - - return await client.query(_options); - } - - Future toggleRepoStar(Repo repo) async { - var document = - repo.viewerHasStarred ? mutations.removeStar : mutations.addStar; - - final MutationOptions _options = MutationOptions( - document: parseString(document), - variables: { - 'starrableId': repo.id, - }, - ); - - return await client.mutate(_options); - } -} diff --git a/examples/flutter_bloc/pubspec.yaml b/examples/flutter_bloc/pubspec.yaml deleted file mode 100644 index 71c40e85c..000000000 --- a/examples/flutter_bloc/pubspec.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: graphql_flutter_bloc_example -description: A new Flutter project. - -publish_to: none -version: 1.0.0+1 - -environment: - sdk: ">=2.10.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - graphql: - path: ../../packages/graphql - graphql_flutter_bloc: ^0.4.4-beta.1 - cupertino_icons: ^0.1.2 - flutter_bloc: ^6.0.5 - freezed_annotation: 0.12.0 - path_provider: ^1.6.18 - hive: ^2.0.0 - equatable: ^0.2.0 - -dev_dependencies: - flutter_test: - sdk: flutter - test: ^1.3.0 - mockito: ^3.0.0 - -flutter: - uses-material-design: true - -dependency_overrides: - graphql: - path: ../../packages/graphql - diff --git a/examples/flutter_bloc/test/bloc_test.dart b/examples/flutter_bloc/test/bloc_test.dart deleted file mode 100644 index 307fc6d31..000000000 --- a/examples/flutter_bloc/test/bloc_test.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'dart:convert'; - -import 'package:graphql/client.dart'; -import 'package:graphql_flutter_bloc_example/blocs/repos/events.dart'; -import 'package:graphql_flutter_bloc_example/blocs/repos/models.dart'; -import 'package:graphql_flutter_bloc_example/blocs/repos/my_repos_bloc.dart'; -import 'package:graphql_flutter_bloc_example/blocs/repos/states.dart'; -import 'package:graphql_flutter_bloc_example/repository.dart'; -import 'package:mockito/mockito.dart'; -import 'package:flutter_test/flutter_test.dart'; - -class MockGithubRepository extends Mock implements GithubRepository {} - -const data = """ -{ - "data": { - "viewer": { - "repositories": { - "nodes": [ - { - "__typename": "Repository", - "id": "MDEwOlJlcG9zaXRvcnkyNDgzOTQ3NA==", - "name": "pq", - "viewerHasStarred": false - }, - { - "__typename": "Repository", - "id": "MDEwOlJlcG9zaXRvcnkzMjkyNDQ0Mw==", - "name": "go-evercookie", - "viewerHasStarred": false - }, - { - "__typename": "Repository", - "id": "MDEwOlJlcG9zaXRvcnkzNTA0NjgyNA==", - "name": "watchbot", - "viewerHasStarred": false - } - ] - } - } - } -} -"""; - -Map decodeGithubResponse = jsonDecode(data); -final List mockedGithubRepos = decodeGithubResponse['data']['viewer'] - ['repositories']['nodes'] as List; - -final List mockedMappedRepos = mockedGithubRepos - .map((dynamic e) => Repo( - id: e['id'] as String, - name: e['name'] as String, - viewerHasStarred: e['viewerHasStarred'] as bool, - )) - .toList(); - -void main() { - group('GithubReposBloc', () { - MyGithubReposBloc repoBloc; - MockGithubRepository githubRepository; - - final numOfRepos = 50; - - setUp(() { - githubRepository = MockGithubRepository(); - repoBloc = MyGithubReposBloc( - githubRepository: githubRepository, - ); - }); - - tearDown(() { - repoBloc.close(); - }); - - test('initial state is loading', () { - expect(repoBloc.state, ReposLoading()); - }); - - group('Fetch Repositories', () { - test('fetch repositories', () { - final results = QueryResult( - data: decodeGithubResponse['data'], - exception: null, - source: QueryResultSource.network, - ); - - when( - githubRepository.getRepositories(numOfRepos), - ).thenAnswer( - (_) => Future.value(results), - ); - - final expected = [ - ReposLoading(), - ReposLoaded(results: mockedMappedRepos), - ]; - - expectLater( - repoBloc.state, - emitsInOrder(expected), - ); - - repoBloc.add(LoadMyRepos(numOfReposToLoad: numOfRepos)); - }); - }); - }); -} diff --git a/examples/flutter_bloc/test/widget_test.dart b/examples/flutter_bloc/test/widget_test.dart deleted file mode 100644 index f3a5de96a..000000000 --- a/examples/flutter_bloc/test/widget_test.dart +++ /dev/null @@ -1,27 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // // Build our app and trigger a frame. - // await tester.pumpWidget(MyApp()); - - // // Verify that our counter starts at 0. - // expect(find.text('0'), findsOneWidget); - // expect(find.text('1'), findsNothing); - - // // Tap the '+' icon and trigger a frame. - // await tester.tap(find.byIcon(Icons.add)); - // await tester.pump(); - - // // Verify that our counter has incremented. - // expect(find.text('0'), findsNothing); - // expect(find.text('1'), findsOneWidget); - }); -} diff --git a/examples/starwars/Makefile b/examples/starwars/Makefile new file mode 100644 index 000000000..67c06b7f8 --- /dev/null +++ b/examples/starwars/Makefile @@ -0,0 +1,21 @@ +CC=dart +CC_UI=flutter + + +default: fmt dep + @echo "Please run \"make server !&\" to run the server" + + +dep: + cd server; $(CC) pub get + $(CC_UI) pub get + +server: + $(CC) pub run graphql_starwars_test_server + +fmt: + $(CC_UI) format . + $(CC_UI) analyze . + +clean: + $(CC_UI) clean diff --git a/examples/starwars/README.md b/examples/starwars/README.md index 36f68c76b..7a2970445 100644 --- a/examples/starwars/README.md +++ b/examples/starwars/README.md @@ -1,8 +1,19 @@ -Start the server: -```dart -cd server && pub get && pub run graphql_starwars_test_server +# Star Wars Demo + +### Server side + +To start the server you need to run the following command: +```bash +make && make server !& ``` -And then `flutter run`, or `flutter run -d chrome` for web support: +### Client side + +To consume the API of the server you can run the server with the following command + +- `flutter run` to run it on the ios device +- `flutter run -d chrome` for web support: + +### Screenshots ![flutter for web image](./for_web.png) diff --git a/examples/starwars/android/app/build.gradle b/examples/starwars/android/app/build.gradle index b72075f84..ad9de98a2 100644 --- a/examples/starwars/android/app/build.gradle +++ b/examples/starwars/android/app/build.gradle @@ -34,8 +34,8 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.starwars" - minSdkVersion 16 - targetSdkVersion 28 + minSdkVersion 19 + targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/examples/starwars/android/app/src/main/AndroidManifest.xml b/examples/starwars/android/app/src/main/AndroidManifest.xml index fffba1b86..2685c8883 100644 --- a/examples/starwars/android/app/src/main/AndroidManifest.xml +++ b/examples/starwars/android/app/src/main/AndroidManifest.xml @@ -7,7 +7,7 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> - + diff --git a/examples/starwars/android/app/src/main/java/com/example/starwars/MainActivity.java b/examples/starwars/android/app/src/main/java/com/example/starwars/MainActivity.java index 70cbcc862..9ff1f9f52 100644 --- a/examples/starwars/android/app/src/main/java/com/example/starwars/MainActivity.java +++ b/examples/starwars/android/app/src/main/java/com/example/starwars/MainActivity.java @@ -1,13 +1,5 @@ package com.example.starwars; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.embedding.android.FlutterActivity; -public class MainActivity extends FlutterActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); - } -} +public class MainActivity extends FlutterActivity { } diff --git a/examples/starwars/android/app/src/main/res/values/styles.xml b/examples/starwars/android/app/src/main/res/values/styles.xml index 00fa4417c..7f287b04b 100644 --- a/examples/starwars/android/app/src/main/res/values/styles.xml +++ b/examples/starwars/android/app/src/main/res/values/styles.xml @@ -5,4 +5,5 @@ Flutter draws its first frame --> @drawable/launch_background + diff --git a/examples/starwars/lib/generated_plugin_registrant.dart b/examples/starwars/lib/generated_plugin_registrant.dart new file mode 100644 index 000000000..3c36ef502 --- /dev/null +++ b/examples/starwars/lib/generated_plugin_registrant.dart @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +// ignore_for_file: directives_ordering +// ignore_for_file: lines_longer_than_80_chars + +import 'package:connectivity_plus_web/connectivity_plus_web.dart'; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +// ignore: public_member_api_docs +void registerPlugins(Registrar registrar) { + ConnectivityPlusPlugin.registerWith(registrar); + registrar.registerMessageHandler(); +} diff --git a/examples/starwars/lib/main.dart b/examples/starwars/lib/main.dart index cf87a5bc7..b8a5bb114 100644 --- a/examples/starwars/lib/main.dart +++ b/examples/starwars/lib/main.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:starwars_app/view/client/graphql_view.dart'; import 'package:universal_platform/universal_platform.dart'; -import './client_provider.dart'; -import './episode/episode_page.dart'; -import './reviews/review_page.dart'; -import './reviews/review_page_list.dart'; +import 'view/episode/episode_page.dart'; +import 'view/review/review_page.dart'; +import 'view/review/review_page_list.dart'; String get host { // https://github.com/flutter/flutter/issues/36126#issuecomment-596215587 diff --git a/examples/starwars/lib/episode/episode.dart b/examples/starwars/lib/model/episode/episode.dart similarity index 100% rename from examples/starwars/lib/episode/episode.dart rename to examples/starwars/lib/model/episode/episode.dart diff --git a/examples/starwars/lib/model/review/review.dart b/examples/starwars/lib/model/review/review.dart new file mode 100644 index 000000000..2771d07ab --- /dev/null +++ b/examples/starwars/lib/model/review/review.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +import 'package:starwars_app/model/episode/episode.dart'; + +class Review { + Review({ + @required this.episode, + @required this.stars, + @required this.id, + this.commentary, + }); + + String id; + Episode episode; + int stars; + String commentary; + + Review copyWith({ + Episode episode, + int stars, + String commentary, + }) { + return Review( + id: id, + episode: episode ?? this.episode, + stars: stars ?? this.stars, + commentary: commentary ?? this.commentary, + ); + } + + Map toJson() { + assert(episode != null && stars != null); + + return { + 'episode': episodeToJson(episode), + 'stars': stars, + 'commentary': commentary, + }; + } + + static Review fromJson(Map map) => Review( + id: map['id'], + episode: episodeFromJson(map['episode'] as String), + stars: map['stars'] as int, + commentary: map['commentary'] as String, + ); +} + +const String Function(Object jsonObject) displayReview = getPrettyJSONString; diff --git a/examples/starwars/lib/client_provider.dart b/examples/starwars/lib/utils/graphql_provider.dart similarity index 60% rename from examples/starwars/lib/client_provider.dart rename to examples/starwars/lib/utils/graphql_provider.dart index 92e0caf54..f22593d87 100644 --- a/examples/starwars/lib/client_provider.dart +++ b/examples/starwars/lib/utils/graphql_provider.dart @@ -32,27 +32,3 @@ ValueNotifier clientFor({ ), ); } - -/// Wraps the root application with the `graphql_flutter` client. -/// We use the cache for all state management. -class ClientProvider extends StatelessWidget { - ClientProvider({ - @required this.child, - @required String uri, - String subscriptionUri, - }) : client = clientFor( - uri: uri, - subscriptionUri: subscriptionUri, - ); - - final Widget child; - final ValueNotifier client; - - @override - Widget build(BuildContext context) { - return GraphQLProvider( - client: client, - child: child, - ); - } -} diff --git a/examples/starwars/lib/view/client/graphql_view.dart b/examples/starwars/lib/view/client/graphql_view.dart new file mode 100644 index 000000000..e65343678 --- /dev/null +++ b/examples/starwars/lib/view/client/graphql_view.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:starwars_app/utils/graphql_provider.dart'; + +/// Wraps the root application with the `graphql_flutter` client. +/// We use the cache for all state management. +class ClientProvider extends StatelessWidget { + ClientProvider({ + @required this.child, + @required String uri, + String subscriptionUri, + }) : client = clientFor( + uri: uri, + subscriptionUri: subscriptionUri, + ); + + final Widget child; + final ValueNotifier client; + + @override + Widget build(BuildContext context) { + return GraphQLProvider( + client: client, + child: child, + ); + } +} diff --git a/examples/starwars/lib/episode/episode_page.dart b/examples/starwars/lib/view/episode/episode_page.dart similarity index 95% rename from examples/starwars/lib/episode/episode_page.dart rename to examples/starwars/lib/view/episode/episode_page.dart index cbe4f480b..c7de2b280 100644 --- a/examples/starwars/lib/episode/episode_page.dart +++ b/examples/starwars/lib/view/episode/episode_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import './episode.dart'; -import './hero_query.dart'; +import '../../model/episode/episode.dart'; +import 'hero_query.dart'; class EpisodePage extends StatefulWidget { static const BottomNavigationBarItem navItem = BottomNavigationBarItem( diff --git a/examples/starwars/lib/episode/hero_query.dart b/examples/starwars/lib/view/episode/hero_query.dart similarity index 97% rename from examples/starwars/lib/episode/hero_query.dart rename to examples/starwars/lib/view/episode/hero_query.dart index de5b0b4fd..7ee9ec3a6 100644 --- a/examples/starwars/lib/episode/hero_query.dart +++ b/examples/starwars/lib/view/episode/hero_query.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; -import './episode.dart'; +import '../../model/episode/episode.dart'; class HeroForEpisode extends StatelessWidget { const HeroForEpisode({@required this.episode}); diff --git a/examples/starwars/lib/reviews/review_page.dart b/examples/starwars/lib/view/review/review_page.dart similarity index 93% rename from examples/starwars/lib/reviews/review_page.dart rename to examples/starwars/lib/view/review/review_page.dart index 89296a94b..4038bd842 100644 --- a/examples/starwars/lib/reviews/review_page.dart +++ b/examples/starwars/lib/view/review/review_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import './review_subscription.dart'; +import 'review_subscription.dart'; class ReviewsPage extends StatelessWidget { static const BottomNavigationBarItem navItem = BottomNavigationBarItem( diff --git a/examples/starwars/lib/reviews/review_page_list.dart b/examples/starwars/lib/view/review/review_page_list.dart similarity index 97% rename from examples/starwars/lib/reviews/review_page_list.dart rename to examples/starwars/lib/view/review/review_page_list.dart index eadbf56f2..23aab63ec 100644 --- a/examples/starwars/lib/reviews/review_page_list.dart +++ b/examples/starwars/lib/view/review/review_page_list.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; -import 'package:starwars_app/reviews/review.dart'; +import 'package:starwars_app/view/review/review_view.dart'; class PagingReviews extends StatelessWidget { static const BottomNavigationBarItem navItem = BottomNavigationBarItem( diff --git a/examples/starwars/lib/reviews/review_subscription.dart b/examples/starwars/lib/view/review/review_subscription.dart similarity index 94% rename from examples/starwars/lib/reviews/review_subscription.dart rename to examples/starwars/lib/view/review/review_subscription.dart index 22d10e22a..3b510c4eb 100644 --- a/examples/starwars/lib/reviews/review_subscription.dart +++ b/examples/starwars/lib/view/review/review_subscription.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; - -import './review.dart'; +import 'package:starwars_app/view/review/review_view.dart'; class ReviewFeed extends StatelessWidget { @override diff --git a/examples/starwars/lib/reviews/review.dart b/examples/starwars/lib/view/review/review_view.dart similarity index 56% rename from examples/starwars/lib/reviews/review.dart rename to examples/starwars/lib/view/review/review_view.dart index 4bd18e3d0..ab3f924b2 100644 --- a/examples/starwars/lib/reviews/review.dart +++ b/examples/starwars/lib/view/review/review_view.dart @@ -1,53 +1,5 @@ -import 'package:meta/meta.dart'; import 'package:flutter/material.dart'; - -import '../episode/episode.dart'; - -class Review { - Review({ - @required this.episode, - @required this.stars, - @required this.id, - this.commentary, - }); - - String id; - Episode episode; - int stars; - String commentary; - - Review copyWith({ - Episode episode, - int stars, - String commentary, - }) { - return Review( - id: id, - episode: episode ?? this.episode, - stars: stars ?? this.stars, - commentary: commentary ?? this.commentary, - ); - } - - Map toJson() { - assert(episode != null && stars != null); - - return { - 'episode': episodeToJson(episode), - 'stars': stars, - 'commentary': commentary, - }; - } - - static Review fromJson(Map map) => Review( - id: map['id'], - episode: episodeFromJson(map['episode'] as String), - stars: map['stars'] as int, - commentary: map['commentary'] as String, - ); -} - -const String Function(Object jsonObject) displayReview = getPrettyJSONString; +import 'package:starwars_app/model/review/review.dart'; class DisplayReviews extends StatefulWidget { const DisplayReviews({ diff --git a/packages/graphql/CHANGELOG.md b/packages/graphql/CHANGELOG.md index 5370c83ec..4ce0fce39 100644 --- a/packages/graphql/CHANGELOG.md +++ b/packages/graphql/CHANGELOG.md @@ -1,3 +1,101 @@ +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-09) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-08) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-07) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-06) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-05) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-04) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-03) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-02) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-01) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-11-30) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-11-29) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-11-28) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-11-27) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-11-26) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + # [5.0.0](https://github.com/zino-app/graphql-flutter/compare/v4.0.1...v5.0.0) (2021-06-07) diff --git a/packages/graphql/README.md b/packages/graphql/README.md index a4bb7edf2..2db482366 100644 --- a/packages/graphql/README.md +++ b/packages/graphql/README.md @@ -41,7 +41,7 @@ As of `v4`, it is built on foundational libraries from the [gql-dart project], i - [Links](#links) - [Composing Links](#composing-links) - [AWS AppSync Support](#aws-appsync-support) - - [Parsing ASTs at build-time](#parsing-asts-at-build-time) + - [Code generation](#code-generation) - [`PersistedQueriesLink` (experimental) :warning: OUT OF SERVICE :warning:](#persistedquerieslink-experimental-warning-out-of-service-warning) **Useful API Docs:** @@ -53,9 +53,8 @@ As of `v4`, it is built on foundational libraries from the [gql-dart project], i First, depend on this package: -```yaml -dependencies: - graphql: ^4.0.0-beta +```console +$ flutter pub add graphql ``` And then import it inside your dart code: @@ -261,6 +260,7 @@ mutation($files: [Upload!]!) { ```dart import "package:http/http.dart" show Multipartfile; +import "package:http_parser/http_parser.dart" show MediaType; // ... @@ -329,10 +329,16 @@ connect: (url, protocols) { } ``` -To supply custom headers to an IO client: +To supply custom headers (not supported on Flutter Web): ```dart -connect: (url, protocols) => - IOWebSocketChannel.connect(url, protocols: protocols, headers: myCustomHeaders) + SocketClient( + wsUrl, + config: SocketClientConfig( + autoReconnect: autoReconnect, + headers: customHeaders, + delayBetweenReconnectionAttempts: delayBetweenReconnectionAttempts, + ), + ); ``` ### `client.watchQuery` and `ObservableQuery` @@ -421,6 +427,79 @@ String customDataIdFromObject(Map data) { } ``` +Normalize requires you to specify the possible types map for fragments to work correctly. This +is a mapping from abstract union and interface types to their concrete object types. E.g. take the +schema + +```graphql + +interface PersonI { + name: String + age: Int +} + +type Employee implements PersonI { + name: String + age: Int + daysOfEmployement: Int +} + +type InStoreCustomer implements PersonI { + name: String + age: Int + numberOfPurchases: Int +} + +type OnlineCustomer implements PersonI { + name: String + age: Int + numberOfPurchases: Int +} + +union CustomerU = OnlineCustomer | InStoreCustomer + +``` + +the possible types map would be: + +```dart +const POSSIBLE_TYPES = const { + 'CustomerU': {'InStoreCustomer', 'OnlineCustomer'}, + 'PersonI': {'Employee', 'InStoreCustomer', 'OnlineCustomer'}, +} + +// Here's how it's parsed to the cache +final client = GraphQLClient( + cache: GraphQLCache( + possibleTypes: POSSIBLE_TYPES, + ), +) +``` + +You can generate the `POSSIBLE_TYPES` map, e.g., using [graphql_codegen](https://pub.dev/packages/graphql_codegen). + +Furthermore, for normalize to correctly resolve the type you should always make sure you're querying the `__typename`. Given the example above a query could look something like + +```graphql + +query { + people { + __typename # Needed to decide where which entry to update in the cache + ... on Employee { + name + age + } + ... on Customer { + name + age + } + } +} + +``` + +if you're not providing the possible type map and introspecting the typename, the cache can't be updated. + ## Direct Cache Access API The [`GraphQLCache`](https://pub.dev/documentation/graphql/latest/graphql/GraphQLCache-class.html) @@ -665,7 +744,7 @@ final Link _link = Link.from([_authLink, _httpLink]); link = Link.split((request) => request.isSubscription, websocketLink, link); ``` -When combining links, **it isimportant to note that**: +When combining links, **it is important to note that**: - Terminating links like `HttpLink` and `WebsocketLink` must come at the end of a route, and will not call links following them. - Link order is very important. In `HttpLink(myEndpoint).concat(AuthLink(getToken: authenticate))`, the `AuthLink` will never be called. @@ -695,21 +774,17 @@ API key, IAM, and Federated provider authorization could be accomplished through - Making a custom link: [Comment on Issue 173](https://github.com/zino-app/graphql-flutter/issues/173#issuecomment-464435942) - AWS JS SDK `auth-link.ts`: [aws-mobile-appsync-sdk-js:auth-link.ts](https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/master/packages/aws-appsync-auth-link/src/auth-link.ts) -## Parsing ASTs at build-time +## Code generation -All `document` arguments are `DocumentNode`s from `gql/ast`. -We supply a `gql` helper for parsing, them, but you can also -parse documents at build-time use `ast_builder` from -[`package:gql_code_gen`](https://pub.dev/packages/gql_code_gen): +This package does not support code-generation out of the box, but [graphql_codegen](https://pub.dev/packages/graphql_codegen) does! -```yaml -dev_dependencies: - gql_code_gen: ^0.1.5 -``` +This package extensions on the client which takes away the struggle of serialization and gives you confidence through type-safety. +It is also more performant than parsing GraphQL queries at runtime. -**`add_star.graphql`**: +For example, by creating the `.graphql` file ```graphql +# add_star.graphql mutation AddStar($starrableId: ID!) { action: addStar(input: { starrableId: $starrableId }) { starrable { @@ -719,19 +794,20 @@ mutation AddStar($starrableId: ID!) { } ``` +after building, you'll be able to execute your mutation on the client as: + ```dart -import 'package:gql/add_star.ast.g.dart' as add_star; +// add_star.dart +import 'add_star.graphql.dart'; -// ... +// .. -final MutationOptions options = MutationOptions( - document: add_star.document, - variables: { - 'starrableId': repositoryID, - }, -); -// ... + await client.mutateAddStar( + OptionsMutationAddStar( + variables: VariablesMutationAddStar(starableId: repositoryID) + ) + ); ``` ## `PersistedQueriesLink` (experimental) :warning: OUT OF SERVICE :warning: @@ -757,16 +833,16 @@ final HttpLink _httpLink = HttpLink( final Link _link = _apqLink.concat(_httpLink); ``` -[build-status-badge]: https://img.shields.io/circleci/build/github/zino-app/graphql-flutter.svg?style=flat-square -[build-status-link]: https://circleci.com/gh/zino-app/graphql-flutter -[coverage-badge]: https://img.shields.io/codecov/c/github/zino-app/graphql-flutter.svg?style=flat-square -[coverage-link]: https://codecov.io/gh/zino-app/graphql-flutter +[build-status-badge]: https://img.shields.io/github/workflow/status/zino-hofmann/graphql-flutter/graphql-flutter%20Tests%20case?style=flat-square +[build-status-link]: https://github.com/zino-hofmann/graphql-flutter/actions +[coverage-badge]: https://img.shields.io/codecov/c/github/zino-hofmann/graphql-flutter/beta?style=flat-square +[coverage-link]: https://app.codecov.io/gh/zino-hofmann/graphql-flutter [version-badge]: https://img.shields.io/pub/v/graphql_flutter.svg?style=flat-square [package-link]: https://pub.dartlang.org/packages/graphql/versions [license-badge]: https://img.shields.io/github/license/zino-app/graphql-flutter.svg?style=flat-square [license-link]: https://github.com/zino-app/graphql-flutter/blob/master/LICENSE [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square -[prs-link]: http://makeapullrequest.com +[prs-link]: https://makeapullrequest.com [github-watch-badge]: https://img.shields.io/github/watchers/zino-app/graphql-flutter.svg?style=flat-square&logo=github&logoColor=ffffff [github-watch-link]: https://github.com/zino-app/graphql-flutter/watchers [github-star-badge]: https://img.shields.io/github/stars/zino-app/graphql-flutter.svg?style=flat-square&logo=github&logoColor=ffffff diff --git a/packages/graphql/example/README.md b/packages/graphql/example/README.md index 41ed36f64..a4e286290 100644 --- a/packages/graphql/example/README.md +++ b/packages/graphql/example/README.md @@ -8,7 +8,7 @@ To run this application: 1. First clone this repository and navigate to this directory 2. Install all dart dependencies -3. replace `` in `bin/local.dart` with your GitHub token +3. replace `` in `lib/local.dart` with your GitHub token ## Usage: ```sh diff --git a/packages/graphql/lib/src/core/_base_options.dart b/packages/graphql/lib/src/core/_base_options.dart index 78322af0a..1640971c1 100644 --- a/packages/graphql/lib/src/core/_base_options.dart +++ b/packages/graphql/lib/src/core/_base_options.dart @@ -1,18 +1,23 @@ -import 'package:graphql/src/core/_data_class.dart'; - +import 'package:collection/collection.dart'; import 'package:gql/ast.dart'; -import 'package:gql_exec/gql_exec.dart'; import 'package:graphql/client.dart'; -import 'package:graphql/src/core/policies.dart'; +import 'package:graphql/src/core/result_parser.dart'; +import 'package:meta/meta.dart'; + +TParsed unprovidedParserFn(_d) => throw UnimplementedError( + "Please provide a parser function to support result parsing.", + ); /// TODO refactor into [Request] container /// Base options. -abstract class BaseOptions extends MutableDataClass { +@immutable +abstract class BaseOptions { BaseOptions({ required this.document, this.variables = const {}, this.operationName, + ResultParserFn? parserFn, Context? context, FetchPolicy? fetchPolicy, ErrorPolicy? errorPolicy, @@ -23,25 +28,26 @@ abstract class BaseOptions extends MutableDataClass { error: errorPolicy, cacheReread: cacheRereadPolicy, ), - context = context ?? Context(); + context = context ?? Context(), + parserFn = parserFn ?? unprovidedParserFn; /// Document containing at least one [OperationDefinitionNode] - DocumentNode document; + final DocumentNode document; /// Name of the executable definition /// /// Must be specified if [document] contains more than one [OperationDefinitionNode] - String? operationName; + final String? operationName; /// A map going from variable name to variable value, where the variables are used /// within the GraphQL query. - Map variables; + final Map variables; /// An optimistic result to eagerly add to the operation stream - Object? optimisticResult; + final Object? optimisticResult; /// Specifies the [Policies] to be used during execution. - Policies policies; + final Policies policies; FetchPolicy? get fetchPolicy => policies.fetch; @@ -50,7 +56,9 @@ abstract class BaseOptions extends MutableDataClass { CacheRereadPolicy? get cacheRereadPolicy => policies.cacheReread; /// Context to be passed to link execution chain. - Context context; + final Context context; + + final ResultParserFn parserFn; // TODO consider inverting this relationship /// Resolve these options into a request @@ -63,7 +71,7 @@ abstract class BaseOptions extends MutableDataClass { context: context, ); - @override + @protected List get properties => [ document, operationName, @@ -71,6 +79,7 @@ abstract class BaseOptions extends MutableDataClass { optimisticResult, policies, context, + parserFn, ]; OperationType get type { @@ -89,4 +98,21 @@ abstract class BaseOptions extends MutableDataClass { bool get isQuery => type == OperationType.query; bool get isMutation => type == OperationType.mutation; bool get isSubscription => type == OperationType.subscription; + + /// [properties] based deep equality check + operator ==(Object other) => + identical(this, other) || + (other is BaseOptions && + runtimeType == other.runtimeType && + const ListEquality( + DeepCollectionEquality(), + ).equals( + other.properties, + properties, + )); + + @override + int get hashCode => const ListEquality( + DeepCollectionEquality(), + ).hash(properties); } diff --git a/packages/graphql/lib/src/core/_data_class.dart b/packages/graphql/lib/src/core/_data_class.dart deleted file mode 100644 index 511f7ddc4..000000000 --- a/packages/graphql/lib/src/core/_data_class.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:meta/meta.dart'; -import "package:collection/collection.dart"; - -/// Helper for making mutable data classes with -/// a [properties] based [equal] helper -/// -/// NOTE: I (@micimize) settled on this helper instead of truly immutable classes -/// because I didn't want to deal with the issue of `copyWith(field: null)`, -/// but also didn't want to commit to adding a true dataclass generator -/// like `freezed` or `built_value` yet. I consider this a stopgap, -/// and think we should eventually have a truly immutable API -abstract class MutableDataClass { - const MutableDataClass(); - - /// identifying properties for the inheriting class - @protected - List get properties; - - /// [properties] based deep equality check - bool equal(MutableDataClass other) => - identical(this, other) || - (runtimeType == other.runtimeType && - const ListEquality( - DeepCollectionEquality(), - ).equals( - other.properties, - properties, - )); -} diff --git a/packages/graphql/lib/src/core/_query_write_handling.dart b/packages/graphql/lib/src/core/_query_write_handling.dart index 4c10fd111..1b551dcd6 100644 --- a/packages/graphql/lib/src/core/_query_write_handling.dart +++ b/packages/graphql/lib/src/core/_query_write_handling.dart @@ -1,12 +1,5 @@ import 'package:graphql/client.dart'; -import 'package:gql_exec/gql_exec.dart'; - -import 'package:graphql/src/core/query_result.dart'; -import 'package:graphql/src/core/policies.dart'; -import 'package:graphql/src/exceptions.dart'; -import 'package:normalize/normalize.dart'; - /// Internal writeQuery wrapper typedef _IntWriteQuery = void Function( Request request, Map? data); diff --git a/packages/graphql/lib/src/core/fetch_more.dart b/packages/graphql/lib/src/core/fetch_more.dart index d9a5f3192..10c1a8fec 100644 --- a/packages/graphql/lib/src/core/fetch_more.dart +++ b/packages/graphql/lib/src/core/fetch_more.dart @@ -2,11 +2,6 @@ import 'dart:async'; import 'package:graphql/client.dart'; -import 'package:graphql/src/core/query_manager.dart'; -import 'package:graphql/src/core/query_options.dart'; -import 'package:graphql/src/core/query_result.dart'; -import 'package:graphql/src/core/policies.dart'; - import 'package:graphql/src/core/_query_write_handling.dart'; /// Fetch more results and then merge them with [previousResult] @@ -17,29 +12,22 @@ import 'package:graphql/src/core/_query_write_handling.dart'; /// /// This is the **Internal Implementation**, /// used by [ObservableQuery] and [GraphQLCLient.fetchMore] -Future fetchMoreImplementation( +Future> fetchMoreImplementation( FetchMoreOptions fetchMoreOptions, { - required QueryOptions originalOptions, + required QueryOptions originalOptions, required QueryManager queryManager, - required QueryResult previousResult, + required QueryResult previousResult, String? queryId, }) async { - // fetch more and udpate + // fetch more and update - final document = (fetchMoreOptions.document ?? originalOptions.document); final request = originalOptions.asRequest; - final combinedOptions = QueryOptions( - fetchPolicy: FetchPolicy.noCache, - errorPolicy: originalOptions.errorPolicy, - document: document, - variables: { - ...originalOptions.variables, - ...fetchMoreOptions.variables, - }, - ); + final combinedOptions = + originalOptions.withFetchMoreOptions(fetchMoreOptions); - QueryResult fetchMoreResult = await queryManager.query(combinedOptions); + QueryResult fetchMoreResult = + await queryManager.query(combinedOptions); try { // combine the query with the new query, using the function provided by the user diff --git a/packages/graphql/lib/src/core/mutation_options.dart b/packages/graphql/lib/src/core/mutation_options.dart index e4b0bb7c6..7b4d836eb 100644 --- a/packages/graphql/lib/src/core/mutation_options.dart +++ b/packages/graphql/lib/src/core/mutation_options.dart @@ -1,16 +1,12 @@ -// ignore_for_file: deprecated_member_use_from_same_package import 'dart:async'; -import 'package:graphql/src/cache/cache.dart'; +import 'package:graphql/client.dart'; import 'package:graphql/src/core/_base_options.dart'; -import 'package:graphql/src/core/observable_query.dart'; import 'package:gql/ast.dart'; -import 'package:gql_exec/gql_exec.dart'; +import 'package:graphql/src/core/result_parser.dart'; -import 'package:graphql/src/exceptions.dart'; -import 'package:graphql/src/core/query_result.dart'; import 'package:graphql/src/utilities/helpers.dart'; -import 'package:graphql/src/core/policies.dart'; +import 'package:meta/meta.dart'; typedef OnMutationCompleted = FutureOr Function(dynamic data); typedef OnMutationUpdate = FutureOr Function( @@ -19,7 +15,8 @@ typedef OnMutationUpdate = FutureOr Function( ); typedef OnError = FutureOr Function(OperationException? error); -class MutationOptions extends BaseOptions { +@immutable +class MutationOptions extends BaseOptions { MutationOptions({ required DocumentNode document, String? operationName, @@ -32,6 +29,7 @@ class MutationOptions extends BaseOptions { this.onCompleted, this.update, this.onError, + ResultParserFn? parserFn, }) : super( fetchPolicy: fetchPolicy, errorPolicy: errorPolicy, @@ -41,6 +39,7 @@ class MutationOptions extends BaseOptions { variables: variables, context: context, optimisticResult: optimisticResult, + parserFn: parserFn, ); final OnMutationCompleted? onCompleted; @@ -48,8 +47,41 @@ class MutationOptions extends BaseOptions { final OnError? onError; @override - List get properties => - [...super.properties, onCompleted, update, onError]; + List get properties => [ + ...super.properties, + onCompleted, + update, + onError, + ]; + + MutationOptions copyWithPolicies(Policies policies) => + MutationOptions( + document: document, + operationName: operationName, + variables: variables, + fetchPolicy: policies.fetch, + errorPolicy: policies.error, + cacheRereadPolicy: policies.cacheReread, + context: context, + optimisticResult: optimisticResult, + onCompleted: onCompleted, + update: update, + onError: onError, + parserFn: parserFn, + ); + + WatchQueryOptions asWatchQueryOptions() => + WatchQueryOptions( + document: document, + operationName: operationName, + variables: variables, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + fetchResults: false, + context: context, + parserFn: parserFn, + ); } /// Handles execution of mutation `update`, `onCompleted`, and `onError` callbacks diff --git a/packages/graphql/lib/src/core/observable_query.dart b/packages/graphql/lib/src/core/observable_query.dart index 01784fafb..a4f421d6a 100644 --- a/packages/graphql/lib/src/core/observable_query.dart +++ b/packages/graphql/lib/src/core/observable_query.dart @@ -1,12 +1,9 @@ import 'dart:async'; import 'package:graphql/client.dart'; +import 'package:graphql/src/utilities/response.dart'; import 'package:meta/meta.dart'; -import 'package:graphql/src/core/query_manager.dart'; -import 'package:graphql/src/core/query_options.dart'; import 'package:graphql/src/core/fetch_more.dart'; -import 'package:graphql/src/core/query_result.dart'; -import 'package:graphql/src/core/policies.dart'; import 'package:graphql/src/scheduler/scheduler.dart'; /// Side effect to register for execution when data is received @@ -62,7 +59,7 @@ enum QueryLifecycle { /// And a handful of internally leveraged methods. /// /// [apollo_oq]: https://www.apollographql.com/docs/react/v3.0-beta/api/core/ObservableQuery/ -class ObservableQuery { +class ObservableQuery { ObservableQuery({ required this.queryManager, required this.options, @@ -71,7 +68,7 @@ class ObservableQuery { _latestWasEagerlyFetched = true; fetchResults(); } - controller = StreamController.broadcast( + controller = StreamController>.broadcast( onListen: onListen, ); } @@ -98,15 +95,15 @@ class ObservableQuery { queryManager.maybeRebroadcastQueries(exclude: this); /// The most recently seen result from this operation's stream - QueryResult? latestResult; + QueryResult? latestResult; QueryLifecycle lifecycle = QueryLifecycle.unexecuted; - WatchQueryOptions options; + WatchQueryOptions options; - late StreamController controller; + late StreamController> controller; - Stream get stream => controller.stream; + Stream> get stream => controller.stream; bool get isCurrentlyPolling => lifecycle == QueryLifecycle.polling; bool get isRefetchSafe { @@ -132,10 +129,13 @@ class ObservableQuery { /// /// **NOTE:** overrides any present non-network-only [FetchPolicy], /// as refetching from the `cache` does not make sense. - Future refetch() { + Future?> refetch() { if (isRefetchSafe) { - addResult(QueryResult.loading(data: latestResult?.data)); - return queryManager.refetchQuery(queryId); + addResult(QueryResult.loading( + data: latestResult?.data, + parserFn: options.parserFn, + )); + return queryManager.refetchQuery(queryId); } throw Exception('Query is not refetch safe'); } @@ -183,8 +183,8 @@ class ObservableQuery { /// Fetch results based on [options.fetchPolicy] /// /// Will [startPolling] if [options.pollInterval] is set - MultiSourceResult fetchResults() { - final MultiSourceResult allResults = + MultiSourceResult fetchResults() { + final MultiSourceResult allResults = queryManager.fetchQueryAsMultiSourceResult(queryId, options); latestResult ??= allResults.eagerResult; @@ -216,8 +216,12 @@ class ObservableQuery { /// it is easy to make mistakes in writing [updateQuery]. /// /// To mitigate this, [FetchMoreOptions.partial] has been provided. - Future fetchMore(FetchMoreOptions fetchMoreOptions) async { - addResult(QueryResult.loading(data: latestResult?.data)); + Future> fetchMore( + FetchMoreOptions fetchMoreOptions) async { + addResult(QueryResult.loading( + data: latestResult?.data, + parserFn: options.parserFn, + )); return fetchMoreImplementation( fetchMoreOptions, @@ -228,6 +232,17 @@ class ObservableQuery { ); } + void addFetchResult( + Response response, + QueryResultSource source, { + bool fromRebroadcast = false, + }) { + addResult( + mapFetchResultToQueryResult(response, options, source: source), + fromRebroadcast: fromRebroadcast, + ); + } + /// Add a [result] to the [stream] unless it was created /// before [lasestResult]. /// @@ -235,7 +250,7 @@ class ObservableQuery { /// if it is set to `null`. /// /// Called internally by the [QueryManager] - void addResult(QueryResult result, {bool fromRebroadcast = false}) { + void addResult(QueryResult result, {bool fromRebroadcast = false}) { // don't overwrite results due to some async/optimism issue if (latestResult != null && latestResult!.timestamp.isAfter(result.timestamp)) { @@ -327,7 +342,7 @@ class ObservableQuery { scheduler!.stopPollingQuery(queryId); } - options.pollInterval = pollInterval; + options = options.copyWithPollInterval(pollInterval); lifecycle = QueryLifecycle.polling; scheduler!.startPollingQuery(options, queryId); } @@ -335,13 +350,18 @@ class ObservableQuery { void stopPolling() { if (isCurrentlyPolling) { scheduler!.stopPollingQuery(queryId); - options.pollInterval = null; + options = options.copyWithPollInterval(null); lifecycle = QueryLifecycle.pollingStopped; } } - set variables(Map variables) => - options.variables = variables; + set variables(Map variables) { + options = options.copyWithVariables(variables); + } + + set optimisticResult(Object? optimisticResult) { + options = options.copyWithOptimisticResult(optimisticResult); + } /// [onData] callbacks have het to be run /// diff --git a/packages/graphql/lib/src/core/query_manager.dart b/packages/graphql/lib/src/core/query_manager.dart index 25cb29310..4b7a41cc6 100644 --- a/packages/graphql/lib/src/core/query_manager.dart +++ b/packages/graphql/lib/src/core/query_manager.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:graphql/src/core/result_parser.dart'; +import 'package:graphql/src/utilities/response.dart'; import 'package:meta/meta.dart'; import 'package:collection/collection.dart'; @@ -48,8 +50,9 @@ class QueryManager { /// prevents rebroadcasting for some intensive bulk operation like [refetchSafeQueries] bool rebroadcastLocked = false; - ObservableQuery watchQuery(WatchQueryOptions options) { - final ObservableQuery observableQuery = ObservableQuery( + ObservableQuery watchQuery( + WatchQueryOptions options) { + final ObservableQuery observableQuery = ObservableQuery( queryManager: this, options: options, ); @@ -59,7 +62,8 @@ class QueryManager { return observableQuery; } - Stream subscribe(SubscriptionOptions options) async* { + Stream> subscribe( + SubscriptionOptions options) async* { assert( options.fetchPolicy != FetchPolicy.cacheOnly, "Cannot subscribe with FetchPolicy.cacheOnly: $options", @@ -70,7 +74,9 @@ class QueryManager { if (options.optimisticResult != null) { // TODO optimisticResults for streams just skip the cache for now yield QueryResult.optimistic( - data: options.optimisticResult as Map?); + data: options.optimisticResult as Map?, + parserFn: options.parserFn, + ); } else if (shouldRespondEagerlyFromCache(options.fetchPolicy)) { final cacheResult = cache.readQuery( request, @@ -80,59 +86,93 @@ class QueryManager { yield QueryResult( source: QueryResultSource.cache, data: cacheResult, + parserFn: options.parserFn, ); } } try { - yield* link.request(request).map((response) { - QueryResult? queryResult; - bool rereadFromCache = false; - try { - queryResult = mapFetchResultToQueryResult( - response, - options, - source: QueryResultSource.network, - ); - - rereadFromCache = attemptCacheWriteFromResponse( - options.policies, - request, - response, - queryResult, - ); - } catch (failure, trace) { - // we set the source to indicate where the source of failure - queryResult ??= QueryResult(source: QueryResultSource.network); - - queryResult.exception = coalesceErrors( - exception: queryResult.exception, - linkException: translateFailure(failure, trace), - ); - } - - if (rereadFromCache) { - // normalize results if previously written - attempCacheRereadIntoResult(request, queryResult); - } - - return queryResult; - }).transform(StreamTransformer.fromHandlers( - handleError: (err, trace, sink) => sink.add(_wrapFailure(err, trace)), - )); + yield* link + .request(request) + .map((response) { + QueryResult? queryResult; + bool rereadFromCache = false; + try { + queryResult = mapFetchResultToQueryResult( + response, + options, + source: QueryResultSource.network, + ); + + rereadFromCache = attemptCacheWriteFromResponse( + options.policies, + request, + response, + queryResult, + ); + } catch (failure, trace) { + // we set the source to indicate where the source of failure + queryResult ??= QueryResult( + source: QueryResultSource.network, + parserFn: options.parserFn, + ); + + queryResult.exception = coalesceErrors( + exception: queryResult.exception, + linkException: translateFailure(failure, trace), + ); + } + + if (rereadFromCache) { + // normalize results if previously written + attempCacheRereadIntoResult(request, queryResult); + } + + return queryResult; + }) + .transform>(StreamTransformer.fromHandlers( + handleError: (err, trace, sink) => sink.add(_wrapFailure( + err, + trace, + options.parserFn, + )), + )) + .map((QueryResult queryResult) { + maybeRebroadcastQueries(); + return queryResult; + }); } catch (ex, trace) { - yield* Stream.fromIterable([_wrapFailure(ex, trace)]); + yield* Stream.fromIterable([ + _wrapFailure( + ex, + trace, + options.parserFn, + ) + ]); } } - Future query(QueryOptions options) async { - final result = await fetchQuery(_oneOffOpId, options); + Future> query( + QueryOptions options) async { + final results = fetchQueryAsMultiSourceResult(_oneOffOpId, options); + final eagerResult = results.eagerResult; + final networkResult = results.networkResult; + if (options.fetchPolicy != FetchPolicy.cacheAndNetwork || + eagerResult.isLoading) { + final result = networkResult ?? eagerResult; + await result; + maybeRebroadcastQueries(); + return result; + } maybeRebroadcastQueries(); - - return result; + if (networkResult is Future>) { + networkResult.then((value) => maybeRebroadcastQueries()); + } + return eagerResult; } - Future mutate(MutationOptions options) async { + Future> mutate( + MutationOptions options) async { final result = await fetchQuery(_oneOffOpId, options); // once the mutation has been process successfully, execute callbacks // before returning the results @@ -154,25 +194,25 @@ class QueryManager { return result; } - Future fetchQuery( + Future> fetchQuery( String queryId, - BaseOptions options, + BaseOptions options, ) async { - final MultiSourceResult allResults = + final MultiSourceResult allResults = fetchQueryAsMultiSourceResult(queryId, options); return allResults.networkResult ?? allResults.eagerResult; } /// Wrap both the `eagerResult` and `networkResult` future in a `MultiSourceResult` /// if the cache policy precludes a network request, `networkResult` will be `null` - MultiSourceResult fetchQueryAsMultiSourceResult( + MultiSourceResult fetchQueryAsMultiSourceResult( String queryId, - BaseOptions options, + BaseOptions options, ) { // create a new request to execute final request = options.asRequest; - final QueryResult eagerResult = _resolveQueryEagerly( + final QueryResult eagerResult = _resolveQueryEagerly( request, queryId, options, @@ -181,6 +221,7 @@ class QueryManager { // _resolveQueryEagerly handles cacheOnly, // so if we're loading + cacheFirst we continue to network return MultiSourceResult( + parserFn: options.parserFn, eagerResult: eagerResult, networkResult: (shouldStopAtCache(options.fetchPolicy) && !eagerResult.isLoading) @@ -191,13 +232,13 @@ class QueryManager { /// Resolve the query on the network, /// negotiating any necessary cache edits / optimistic cleanup - Future _resolveQueryOnNetwork( + Future> _resolveQueryOnNetwork( Request request, String queryId, - BaseOptions options, + BaseOptions options, ) async { Response response; - QueryResult? queryResult; + QueryResult? queryResult; bool rereadFromCache = false; @@ -219,7 +260,10 @@ class QueryManager { ); } catch (failure, trace) { // we set the source to indicate where the source of failure - queryResult ??= QueryResult(source: QueryResultSource.network); + queryResult ??= QueryResult( + source: QueryResultSource.network, + parserFn: options.parserFn, + ); queryResult.exception = coalesceErrors( exception: queryResult.exception, @@ -245,12 +289,14 @@ class QueryManager { /// Add an eager cache response to the stream if possible, /// based on `fetchPolicy` and `optimisticResults` - QueryResult _resolveQueryEagerly( + QueryResult _resolveQueryEagerly( Request request, String queryId, - BaseOptions options, + BaseOptions options, ) { - QueryResult queryResult = QueryResult.loading(); + QueryResult queryResult = QueryResult.loading( + parserFn: options.parserFn, + ); try { if (options.optimisticResult != null) { @@ -258,6 +304,7 @@ class QueryManager { request, queryId: queryId, optimisticResult: options.optimisticResult, + options: options, ); } @@ -271,6 +318,7 @@ class QueryManager { queryResult = QueryResult( data: data, source: QueryResultSource.cache, + parserFn: options.parserFn, ); } @@ -278,6 +326,7 @@ class QueryManager { queryResult.isLoading) { queryResult = QueryResult( source: QueryResultSource.cache, + parserFn: options.parserFn, exception: OperationException( linkException: CacheMissException( 'Could not resolve the given request against the cache. (FetchPolicy.cacheOnly)', @@ -312,12 +361,11 @@ class QueryManager { /// Refetch the [ObservableQuery] referenced by [queryId], /// overriding any present non-network-only [FetchPolicy]. - Future refetchQuery(String queryId) { - final WatchQueryOptions options = queries[queryId]!.options.copy(); + Future?> refetchQuery(String queryId) { + WatchQueryOptions options = + queries[queryId]!.options as WatchQueryOptions; if (!willAlwaysExecuteOnNetwork(options.fetchPolicy)) { - options.policies = options.policies.copyWith( - fetch: FetchPolicy.networkOnly, - ); + options = options.copyWithFetchPolicy(FetchPolicy.networkOnly); } // create a new request to execute @@ -350,12 +398,13 @@ class QueryManager { /// Will [maybeRebroadcastQueries] from [ObservableQuery.addResult] if the [cache] has flagged the need to. /// /// Queries are registered via [setQuery] and [watchQuery] - void addQueryResult( + void addQueryResult( Request request, String? queryId, - QueryResult queryResult, + QueryResult queryResult, ) { - final ObservableQuery? observableQuery = getQuery(queryId); + final ObservableQuery? observableQuery = + getQuery(queryId) as ObservableQuery?; if (observableQuery != null && !observableQuery.controller.isClosed) { observableQuery.addResult(queryResult); @@ -363,13 +412,15 @@ class QueryManager { } /// Create an optimstic result for the query specified by `queryId`, if it exists - QueryResult _getOptimisticQueryResult( + QueryResult _getOptimisticQueryResult( Request request, { required String queryId, required Object? optimisticResult, + required BaseOptions options, }) { - QueryResult queryResult = QueryResult( + QueryResult queryResult = QueryResult( source: QueryResultSource.optimisticResult, + parserFn: options.parserFn, ); attemptCacheWriteFromClient( @@ -415,19 +466,16 @@ class QueryManager { return false; } - for (ObservableQuery query in queries.values) { + for (var query in queries.values) { if (query != exclude && query.isRebroadcastSafe) { final cachedData = cache.readQuery( query.options.asRequest, optimistic: query.options.policies.mergeOptimisticData, ); if (_cachedDataHasChangedFor(query, cachedData)) { - query.addResult( - mapFetchResultToQueryResult( - Response(data: cachedData), - query.options, - source: QueryResultSource.cache, - ), + query.addFetchResult( + Response(data: cachedData), + QueryResultSource.cache, fromRebroadcast: true, ); } @@ -461,50 +509,16 @@ class QueryManager { return requestId; } - - QueryResult mapFetchResultToQueryResult( - Response response, - BaseOptions options, { - required QueryResultSource source, - }) { - List? errors; - dynamic data; - - // check if there are errors and apply the error policy if so - // in a nutshell: `ignore` swallows errors, `none` swallows data - if (response.errors != null && response.errors!.isNotEmpty) { - switch (options.errorPolicy) { - case ErrorPolicy.all: - // handle both errors and data - errors = response.errors; - data = response.data; - break; - case ErrorPolicy.ignore: - // ignore errors - data = response.data; - break; - case ErrorPolicy.none: - default: - // TODO not actually sure if apollo even casts graphql errors in `none` mode, - // it's also kind of legacy - errors = response.errors; - break; - } - } else { - data = response.data; - } - - return QueryResult( - data: data, - context: response.context, - source: source, - exception: coalesceErrors(graphqlErrors: errors), - ); - } } -QueryResult _wrapFailure(dynamic ex, trace) => QueryResult( +QueryResult _wrapFailure( + dynamic ex, + trace, + ResultParserFn parserFn, +) => + QueryResult( // we set the source to indicate where the source of failure source: QueryResultSource.network, exception: coalesceErrors(linkException: translateFailure(ex, trace)), + parserFn: parserFn, ); diff --git a/packages/graphql/lib/src/core/query_options.dart b/packages/graphql/lib/src/core/query_options.dart index 7f22f1852..89f7ac552 100644 --- a/packages/graphql/lib/src/core/query_options.dart +++ b/packages/graphql/lib/src/core/query_options.dart @@ -1,15 +1,16 @@ -// ignore_for_file: deprecated_member_use_from_same_package +import 'package:gql/language.dart'; import 'package:graphql/src/core/_base_options.dart'; +import 'package:graphql/src/core/result_parser.dart'; import 'package:graphql/src/utilities/helpers.dart'; import 'package:gql/ast.dart'; -import 'package:gql_exec/gql_exec.dart'; import 'package:graphql/client.dart'; -import 'package:graphql/src/core/policies.dart'; +import 'package:meta/meta.dart'; /// Query options. -class QueryOptions extends BaseOptions { +@immutable +class QueryOptions extends BaseOptions { QueryOptions({ required DocumentNode document, String? operationName, @@ -20,6 +21,7 @@ class QueryOptions extends BaseOptions { Object? optimisticResult, this.pollInterval, Context? context, + ResultParserFn? parserFn, }) : super( fetchPolicy: fetchPolicy, errorPolicy: errorPolicy, @@ -29,15 +31,35 @@ class QueryOptions extends BaseOptions { variables: variables, context: context, optimisticResult: optimisticResult, + parserFn: parserFn, ); /// The time interval on which this query should be re-fetched from the server. - Duration? pollInterval; + final Duration? pollInterval; @override - List get properties => [...super.properties, pollInterval]; + List get properties => [ + ...super.properties, + pollInterval, + ]; - WatchQueryOptions asWatchQueryOptions({bool fetchResults = true}) => + QueryOptions withFetchMoreOptions( + FetchMoreOptions fetchMoreOptions, + ) => + QueryOptions( + document: fetchMoreOptions.document ?? document, + operationName: operationName, + fetchPolicy: FetchPolicy.noCache, + errorPolicy: errorPolicy, + parserFn: parserFn, + context: context, + variables: { + ...variables, + ...fetchMoreOptions.variables, + }, + ); + + WatchQueryOptions asWatchQueryOptions({bool fetchResults = true}) => WatchQueryOptions( document: document, operationName: operationName, @@ -49,10 +71,25 @@ class QueryOptions extends BaseOptions { fetchResults: fetchResults, context: context, optimisticResult: optimisticResult, + parserFn: parserFn, + ); + + QueryOptions copyWithPolicies(Policies policies) => QueryOptions( + document: document, + operationName: operationName, + variables: variables, + fetchPolicy: policies.fetch, + errorPolicy: policies.error, + cacheRereadPolicy: policies.cacheReread, + optimisticResult: optimisticResult, + pollInterval: pollInterval, + context: context, + parserFn: parserFn, ); } -class SubscriptionOptions extends BaseOptions { +@immutable +class SubscriptionOptions extends BaseOptions { SubscriptionOptions({ required DocumentNode document, String? operationName, @@ -62,6 +99,7 @@ class SubscriptionOptions extends BaseOptions { CacheRereadPolicy? cacheRereadPolicy, Object? optimisticResult, Context? context, + ResultParserFn? parserFn, }) : super( fetchPolicy: fetchPolicy, errorPolicy: errorPolicy, @@ -71,13 +109,24 @@ class SubscriptionOptions extends BaseOptions { variables: variables, context: context, optimisticResult: optimisticResult, + parserFn: parserFn, ); - - /// An optimistic first result to eagerly add to the subscription stream - Object? optimisticResult; + SubscriptionOptions copyWithPolicies(Policies policies) => + SubscriptionOptions( + document: document, + operationName: operationName, + variables: variables, + fetchPolicy: policies.fetch, + errorPolicy: policies.error, + cacheRereadPolicy: policies.cacheReread, + optimisticResult: optimisticResult, + context: context, + parserFn: parserFn, + ); } -class WatchQueryOptions extends QueryOptions { +@immutable +class WatchQueryOptions extends QueryOptions { WatchQueryOptions({ required DocumentNode document, String? operationName, @@ -91,6 +140,7 @@ class WatchQueryOptions extends QueryOptions { this.carryForwardDataOnException = true, bool? eagerlyFetchResults, Context? context, + ResultParserFn? parserFn, }) : eagerlyFetchResults = eagerlyFetchResults ?? fetchResults, super( document: document, @@ -102,24 +152,103 @@ class WatchQueryOptions extends QueryOptions { pollInterval: pollInterval, context: context, optimisticResult: optimisticResult, + parserFn: parserFn, ); /// Whether or not to fetch results - bool fetchResults; + final bool fetchResults; /// Whether to [fetchResults] immediately on instantiation. /// Defaults to [fetchResults]. - bool eagerlyFetchResults; + final bool eagerlyFetchResults; /// carry forward previous data in the result of errors and no data. /// defaults to `true`. - bool carryForwardDataOnException; + final bool carryForwardDataOnException; @override - List get properties => - [...super.properties, fetchResults, eagerlyFetchResults]; + List get properties => [ + ...super.properties, + fetchResults, + eagerlyFetchResults, + carryForwardDataOnException, + ]; + + WatchQueryOptions copyWithFetchPolicy( + FetchPolicy? fetchPolicy, + ) => + WatchQueryOptions( + document: document, + operationName: operationName, + variables: variables, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult, + pollInterval: pollInterval, + fetchResults: fetchResults, + eagerlyFetchResults: eagerlyFetchResults, + carryForwardDataOnException: carryForwardDataOnException, + context: context, + parserFn: parserFn, + ); + WatchQueryOptions copyWithPolicies( + Policies policies, + ) => + WatchQueryOptions( + document: document, + operationName: operationName, + variables: variables, + fetchPolicy: policies.fetch, + errorPolicy: policies.error, + cacheRereadPolicy: policies.cacheReread, + optimisticResult: optimisticResult, + pollInterval: pollInterval, + fetchResults: fetchResults, + eagerlyFetchResults: eagerlyFetchResults, + carryForwardDataOnException: carryForwardDataOnException, + context: context, + parserFn: parserFn, + ); + + WatchQueryOptions copyWithPollInterval(Duration? pollInterval) => + WatchQueryOptions( + document: document, + operationName: operationName, + variables: variables, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult, + pollInterval: pollInterval, + fetchResults: fetchResults, + eagerlyFetchResults: eagerlyFetchResults, + carryForwardDataOnException: carryForwardDataOnException, + context: context, + parserFn: parserFn, + ); + + WatchQueryOptions copyWithVariables( + Map variables) => + WatchQueryOptions( + document: document, + operationName: operationName, + variables: variables, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult, + pollInterval: pollInterval, + fetchResults: fetchResults, + eagerlyFetchResults: eagerlyFetchResults, + carryForwardDataOnException: carryForwardDataOnException, + context: context, + parserFn: parserFn, + ); - WatchQueryOptions copy() => WatchQueryOptions( + WatchQueryOptions copyWithOptimisticResult( + Object? optimisticResult) => + WatchQueryOptions( document: document, operationName: operationName, variables: variables, @@ -132,6 +261,7 @@ class WatchQueryOptions extends QueryOptions { eagerlyFetchResults: eagerlyFetchResults, carryForwardDataOnException: carryForwardDataOnException, context: context, + parserFn: parserFn, ); } @@ -141,6 +271,7 @@ class WatchQueryOptions extends QueryOptions { /// it is easy to make mistakes in writing [updateQuery]. /// /// To mitigate this, [FetchMoreOptions.partial] has been provided. +@immutable class FetchMoreOptions { FetchMoreOptions({ this.document, @@ -164,13 +295,13 @@ class FetchMoreOptions { updateQuery: partialUpdater(updateQuery), ); - DocumentNode? document; + final DocumentNode? document; - Map variables; + final Map variables; /// Strategy for merging the fetchMore result data /// with the result data already in the cache - UpdateQuery updateQuery; + final UpdateQuery updateQuery; /// Wrap an [UpdateQuery] in a [deeplyMergeLeft] of the `previousResultData`. static UpdateQuery partialUpdater(UpdateQuery update) => diff --git a/packages/graphql/lib/src/core/query_result.dart b/packages/graphql/lib/src/core/query_result.dart index 2c8aa5a8f..0bdf3c9bd 100644 --- a/packages/graphql/lib/src/core/query_result.dart +++ b/packages/graphql/lib/src/core/query_result.dart @@ -1,6 +1,6 @@ import 'dart:async' show FutureOr; import 'package:graphql/client.dart'; -import 'package:graphql/src/exceptions.dart'; +import 'package:graphql/src/core/result_parser.dart'; /// The source of the result data contained /// @@ -38,33 +38,41 @@ final _eagerSources = { }; /// A single operation result -class QueryResult { +class QueryResult { QueryResult({ this.data, this.exception, this.context = const Context(), + required this.parserFn, required this.source, }) : timestamp = DateTime.now(); /// Unexecuted singleton, used as a placeholder for mutations, /// etc. - static final unexecuted = QueryResult(source: null) - ..timestamp = DateTime.fromMillisecondsSinceEpoch(0); + static final unexecuted = QueryResult( + source: null, + parserFn: (d) => + throw UnimplementedError("Unexecuted query data can not be parsed."), + )..timestamp = DateTime.fromMillisecondsSinceEpoch(0); factory QueryResult.loading({ Map? data, + required ResultParserFn parserFn, }) => QueryResult( data: data, source: QueryResultSource.loading, + parserFn: parserFn, ); factory QueryResult.optimistic({ Map? data, + required ResultParserFn parserFn, }) => QueryResult( data: data, source: QueryResultSource.optimisticResult, + parserFn: parserFn, ); DateTime timestamp; @@ -83,6 +91,8 @@ class QueryResult { OperationException? exception; + ResultParserFn parserFn; + /// [data] has yet to be specified from any source /// for the _most recent_ operation /// (including [QueryResultSource.optimisticResult]) @@ -108,6 +118,17 @@ class QueryResult { /// Whether the response includes an [exception] bool get hasException => (exception != null); + /// If a parserFn is provided, this getter can be used to fetch the parsed data. + TParsed? get parsedData { + final data = this.data; + final parserFn = this.parserFn; + + if (data == null) { + return null; + } + return parserFn(data); + } + @override String toString() => 'QueryResult(' 'source: $source, ' @@ -118,16 +139,17 @@ class QueryResult { ')'; } -class MultiSourceResult { +class MultiSourceResult { MultiSourceResult({ - QueryResult? eagerResult, + QueryResult? eagerResult, this.networkResult, - }) : eagerResult = eagerResult ?? QueryResult.loading(), + required ResultParserFn parserFn, + }) : eagerResult = eagerResult ?? QueryResult.loading(parserFn: parserFn), assert( eagerResult!.source != QueryResultSource.network, 'An eager result cannot be gotten from the network', ); - QueryResult eagerResult; - FutureOr? networkResult; + QueryResult eagerResult; + FutureOr>? networkResult; } diff --git a/packages/graphql/lib/src/core/result_parser.dart b/packages/graphql/lib/src/core/result_parser.dart new file mode 100644 index 000000000..9357b2cd6 --- /dev/null +++ b/packages/graphql/lib/src/core/result_parser.dart @@ -0,0 +1 @@ +typedef ResultParserFn = TResult Function(Map data); diff --git a/packages/graphql/lib/src/exceptions/exceptions.dart b/packages/graphql/lib/src/exceptions/exceptions.dart index 772e8a572..daa26e91c 100644 --- a/packages/graphql/lib/src/exceptions/exceptions.dart +++ b/packages/graphql/lib/src/exceptions/exceptions.dart @@ -15,6 +15,5 @@ LinkException translateFailure(dynamic failure, StackTrace trace) { if (failure is LinkException) { return failure; } - return network.translateFailure(failure) ?? - UnknownException(failure, trace); + return network.translateFailure(failure) ?? UnknownException(failure, trace); } diff --git a/packages/graphql/lib/src/exceptions/exceptions_next.dart b/packages/graphql/lib/src/exceptions/exceptions_next.dart index fca256b04..f8959921f 100644 --- a/packages/graphql/lib/src/exceptions/exceptions_next.dart +++ b/packages/graphql/lib/src/exceptions/exceptions_next.dart @@ -4,9 +4,6 @@ import 'package:graphql/client.dart'; /// these should be the only exceptions we need import 'package:meta/meta.dart'; -import 'package:gql_link/gql_link.dart' show LinkException, ServerException; -import 'package:gql_exec/gql_exec.dart' show GraphQLError, Request, Response; - export 'package:gql_exec/gql_exec.dart' show GraphQLError; export 'package:normalize/normalize.dart' show PartialDataException; diff --git a/packages/graphql/lib/src/graphql_client.dart b/packages/graphql/lib/src/graphql_client.dart index 52ab3c648..dcb54fc6f 100644 --- a/packages/graphql/lib/src/graphql_client.dart +++ b/packages/graphql/lib/src/graphql_client.dart @@ -87,10 +87,10 @@ class GraphQLClient implements GraphQLDataProxy { /// observableQuery.close(); /// ``` /// {@end-tool} - ObservableQuery watchQuery(WatchQueryOptions options) { - options.policies = - defaultPolicies.watchQuery.withOverrides(options.policies); - return queryManager.watchQuery(options); + ObservableQuery watchQuery( + WatchQueryOptions options) { + final policies = defaultPolicies.watchQuery.withOverrides(options.policies); + return queryManager.watchQuery(options.copyWithPolicies(policies)); } /// [watchMutation] is the same as [watchQuery], but with a different [defaultPolicies] that are more appropriate for mutations. @@ -98,10 +98,11 @@ class GraphQLClient implements GraphQLDataProxy { /// This is a stop-gap solution to the problems created by the reliance of `graphql_flutter` on [ObservableQuery] for mutations. /// /// For more details, see https://github.com/zino-app/graphql-flutter/issues/774 - ObservableQuery watchMutation(WatchQueryOptions options) { - options.policies = + ObservableQuery watchMutation( + WatchQueryOptions options) { + final policies = defaultPolicies.watchMutation.withOverrides(options.policies); - return queryManager.watchQuery(options); + return queryManager.watchQuery(options.copyWithPolicies(policies)); } /// This resolves a single query according to the [QueryOptions] specified and @@ -144,16 +145,19 @@ class GraphQLClient implements GraphQLDataProxy { /// ``` /// {@end-tool} - Future query(QueryOptions options) { - options.policies = defaultPolicies.query.withOverrides(options.policies); - return queryManager.query(options); + Future> query( + QueryOptions options, + ) async { + final policies = defaultPolicies.query.withOverrides(options.policies); + return await queryManager.query(options.copyWithPolicies(policies)); } /// This resolves a single mutation according to the [MutationOptions] specified and /// returns a [Future] which resolves with the [QueryResult] or throws an [Exception]. - Future mutate(MutationOptions options) { - options.policies = defaultPolicies.mutate.withOverrides(options.policies); - return queryManager.mutate(options); + Future> mutate( + MutationOptions options) async { + final policies = defaultPolicies.mutate.withOverrides(options.policies); + return await queryManager.mutate(options.copyWithPolicies(policies)); } /// This subscribes to a GraphQL subscription according to the options specified and returns a @@ -192,11 +196,12 @@ class GraphQLClient implements GraphQLDataProxy { /// }); /// ``` /// {@end-tool} - Stream subscribe(SubscriptionOptions options) { - options.policies = defaultPolicies.subscribe.withOverrides( + Stream> subscribe( + SubscriptionOptions options) { + final policies = defaultPolicies.subscribe.withOverrides( options.policies, ); - return queryManager.subscribe(options); + return queryManager.subscribe(options.copyWithPolicies(policies)); } /// Fetch more results and then merge them with the given [previousResult] @@ -207,17 +212,18 @@ class GraphQLClient implements GraphQLDataProxy { /// /// To mitigate this, [FetchMoreOptions.partial] has been provided. @experimental - Future fetchMore( + Future> fetchMore( FetchMoreOptions fetchMoreOptions, { - required QueryOptions originalOptions, - required QueryResult previousResult, - }) => - fetchMoreImplementation( - fetchMoreOptions, - originalOptions: originalOptions, - previousResult: previousResult, - queryManager: queryManager, - ); + required QueryOptions originalOptions, + required QueryResult previousResult, + }) async { + return await fetchMoreImplementation( + fetchMoreOptions, + originalOptions: originalOptions, + previousResult: previousResult, + queryManager: queryManager, + ); + } /// pass through to [cache.readQuery] readQuery(request, {optimistic = true}) => diff --git a/packages/graphql/lib/src/links/auth_link.dart b/packages/graphql/lib/src/links/auth_link.dart index ba5a5b37a..7c50e6d9d 100644 --- a/packages/graphql/lib/src/links/auth_link.dart +++ b/packages/graphql/lib/src/links/auth_link.dart @@ -1,9 +1,6 @@ import 'dart:async'; import 'package:graphql/client.dart'; -import "package:gql_exec/gql_exec.dart"; -import "package:gql_http_link/gql_http_link.dart"; -import "package:gql_link/gql_link.dart"; import "package:gql_transform_link/gql_transform_link.dart"; typedef _RequestTransformer = FutureOr Function(Request request); diff --git a/packages/graphql/lib/src/links/gql_links.dart b/packages/graphql/lib/src/links/gql_links.dart index 715f91660..4a83fded2 100644 --- a/packages/graphql/lib/src/links/gql_links.dart +++ b/packages/graphql/lib/src/links/gql_links.dart @@ -1,6 +1,4 @@ export 'package:gql_link/gql_link.dart'; - export 'package:gql_http_link/gql_http_link.dart'; -// export 'package:gql_websocket_link/gql_websocket_link.dart'; export 'package:gql_error_link/gql_error_link.dart'; export 'package:gql_dedupe_link/gql_dedupe_link.dart'; diff --git a/packages/graphql/lib/src/links/links.dart b/packages/graphql/lib/src/links/links.dart index 1ead5ab31..87da7132f 100644 --- a/packages/graphql/lib/src/links/links.dart +++ b/packages/graphql/lib/src/links/links.dart @@ -1,6 +1,4 @@ // Reexport all gql_links export 'package:graphql/src/links/gql_links.dart'; - export 'package:graphql/src/links/auth_link.dart'; - export 'package:graphql/src/links/websocket_link/websocket_link.dart'; diff --git a/packages/graphql/lib/src/links/websocket_link/websocket_client.dart b/packages/graphql/lib/src/links/websocket_link/websocket_client.dart index 0f9ad463d..eeec5b8c4 100644 --- a/packages/graphql/lib/src/links/websocket_link/websocket_client.dart +++ b/packages/graphql/lib/src/links/websocket_link/websocket_client.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:graphql/src/links/gql_links.dart'; +import 'package:graphql/src/utilities/platform.dart'; import 'package:meta/meta.dart'; import 'package:graphql/src/core/query_options.dart' show WithType; @@ -22,7 +23,7 @@ import './websocket_messages.dart'; typedef GetInitPayload = FutureOr Function(); /// A definition for functions that returns a connected [WebSocketChannel] -typedef WebSocketConnect = WebSocketChannel Function( +typedef WebSocketConnect = FutureOr Function( Uri uri, Iterable? protocols, ); @@ -48,7 +49,8 @@ class SocketClientConfig { this.inactivityTimeout = const Duration(seconds: 30), this.delayBetweenReconnectionAttempts = const Duration(seconds: 5), this.initialPayload, - @experimental this.connect = defaultConnect, + this.headers, + this.connectFn, }); /// Serializer used to serialize request @@ -85,7 +87,7 @@ class SocketClientConfig { /// Warning: if you want to listen to the listen to the stream, /// wrap your channel with our [GraphQLWebSocketChannel] using the `.forGraphQL()` helper: /// ```dart - /// connect: (url, protocols) { + /// connectFn: (url, protocols) { /// var channel = WebSocketChannel.connect(url, protocols: protocols) /// // without this line, our client won't be able to listen to stream events, /// // because you are already listening. @@ -94,19 +96,26 @@ class SocketClientConfig { /// return channel; /// } /// ``` - /// - /// To supply custom headers to an IO client: - /// ```dart - /// connect: (url, protocols) => - /// IOWebSocketChannel.connect(url, protocols: protocols, headers: myCustomHeaders) - /// ``` - final WebSocketConnect connect; - - static WebSocketChannel defaultConnect( - Uri uri, - Iterable? protocols, - ) => - WebSocketChannel.connect(uri, protocols: protocols).forGraphQL(); + final WebSocketConnect? connectFn; + + /// Custom header to add inside the client + final Map? headers; + + /// Function to define another connection without call directly + /// the connection function + FutureOr connect( + {required Uri uri, + Iterable? protocols, + Map? headers}) { + if (connectFn != null) { + return connectFn!(uri, protocols); + } + return defaultConnectPlatform( + uri, + protocols, + headers: headers ?? this.headers, + ); + } /// Payload to be sent with the connection_init request. /// @@ -147,7 +156,6 @@ class SocketClient { SocketClient( this.url, { this.protocols = const ['graphql-ws'], - WebSocketConnect? connect, this.config = const SocketClientConfig(), @visibleForTesting this.randomBytesForUuid, @visibleForTesting this.onMessage, @@ -168,6 +176,7 @@ class SocketClient { HashMap(); bool _connectionWasLost = false; + bool _wasDisposed = false; Timer? _reconnectTimer; @@ -195,36 +204,44 @@ class SocketClient { _keepAliveSubscription = messages.whereType().timeout( config.inactivityTimeout!, onTimeout: (EventSink event) { - print( - "Haven't received keep alive message for ${config.inactivityTimeout!.inSeconds} seconds. Disconnecting..", - ); event.close(); - socketChannel!.sink.close(ws_status.goingAway); - _connectionStateController.add(SocketConnectionState.notConnected); + unawaited(_closeSocketChannel()); }, ).listen(null); } + Future _closeSocketChannel() async { + // avoid race condition in onCancel by setting socket connection + // state to notConnected prior to closing socket. This ensures we don't + // attempt to send a message over the channel that we're closing + // if we are forcefully closing the socket + if (!_connectionStateController.isClosed && + _connectionStateController.value != + SocketConnectionState.notConnected) { + _connectionStateController.add(SocketConnectionState.notConnected); + } + await socketChannel?.sink.close(ws_status.normalClosure); + } + /// Connects to the server. /// /// If this instance is disposed, this method does nothing. Future _connect() async { final InitOperation initOperation = await config.initOperation; - if (_connectionStateController.isClosed) { + if (_connectionStateController.isClosed || _wasDisposed) { return; } _connectionStateController.add(SocketConnectionState.connecting); - print('Connecting to websocket: $url...'); try { // Even though config.connect is sync, we call async in order to make the // SocketConnectionState.connected attribution not overload SocketConnectionState.connecting - socketChannel = - await config.connect(Uri.parse(url), protocols).forGraphQL(); + var connection = + await config.connect(uri: Uri.parse(url), protocols: protocols); + socketChannel = connection.forGraphQL(); _connectionStateController.add(SocketConnectionState.connected); - print('Connected to websocket.'); _write(initOperation); if (config.inactivityTimeout != null) { @@ -234,7 +251,9 @@ class SocketClient { _messageSubscription = _messages.listen( onMessage, onDone: onConnectionLost, - cancelOnError: true, + // onDone will not be triggered if the subscription is + // auto-cancelled on error; make sure to pass false + cancelOnError: false, onError: onStreamError, ); @@ -250,8 +269,8 @@ class SocketClient { } } - void onConnectionLost([e]) { - socketChannel?.sink.close(ws_status.goingAway); + void onConnectionLost([e]) async { + await _closeSocketChannel(); if (e != null) { print('There was an error causing connection lost: $e'); } @@ -260,24 +279,17 @@ class SocketClient { _keepAliveSubscription?.cancel(); _messageSubscription?.cancel(); - if (_connectionStateController.isClosed) { + if (_connectionStateController.isClosed || _wasDisposed) { return; } _connectionWasLost = true; _subscriptionInitializers.values.forEach((s) => s.hasBeenTriggered = false); - if (_connectionStateController.value != - SocketConnectionState.notConnected) { - _connectionStateController.add(SocketConnectionState.notConnected); - } - - if (config.autoReconnect && !_connectionStateController.isClosed) { + if (config.autoReconnect && + !_connectionStateController.isClosed && + !_wasDisposed) { if (config.delayBetweenReconnectionAttempts != null) { - print( - 'Scheduling to connect in ${config.delayBetweenReconnectionAttempts!.inSeconds} seconds...', - ); - _reconnectTimer = Timer( config.delayBetweenReconnectionAttempts!, () { @@ -297,11 +309,15 @@ class SocketClient { /// Use this method if you'd like to disconnect from the specified server permanently, /// and you'd like to connect to another server instead of the current one. Future dispose() async { + // Make sure we do not attempt to reconnect when we close the socket + // and onConnectionLost is called (as part of onDone) + _wasDisposed = true; print('Disposing socket client..'); _reconnectTimer?.cancel(); + _keepAliveSubscription?.cancel(); await Future.wait([ - socketChannel?.sink.close(ws_status.goingAway), + _closeSocketChannel(), _messageSubscription?.cancel(), _connectionStateController.close(), ].where((future) => future != null).cast>().toList()); @@ -379,7 +395,7 @@ class SocketClient { return false; }, - ).takeWhile((_) => !response.isClosed); + ).takeWhile((_) => (!response.isClosed && !_wasDisposed)); final Stream subscriptionComplete = addTimeout ? dataErrorComplete @@ -388,7 +404,6 @@ class SocketClient { .timeout( config.queryAndMutationTimeout!, onTimeout: (EventSink event) { - print('Request timed out.'); response.addError(TimeoutException('Request timed out.')); event.close(); response.close(); @@ -448,7 +463,7 @@ class SocketClient { } void _defaultOnStreamError(Object error, StackTrace st) { - print('[SocketClient] message stream ecnountered error: $error\n' + print('[SocketClient] message stream encountered error: $error\n' 'stacktrace:\n${st.toString()}'); } diff --git a/packages/graphql/lib/src/utilities/platform.dart b/packages/graphql/lib/src/utilities/platform.dart index 2231dfd0e..8abbc3511 100644 --- a/packages/graphql/lib/src/utilities/platform.dart +++ b/packages/graphql/lib/src/utilities/platform.dart @@ -1,3 +1 @@ -export './platform_stub.dart' - if (dart.library.html) './platform_html.dart' - if (dart.library.io) './platform_io.dart'; +export './platform_html.dart' if (dart.library.io) './platform_io.dart'; diff --git a/packages/graphql/lib/src/utilities/platform_html.dart b/packages/graphql/lib/src/utilities/platform_html.dart index 1d05fa373..5e1882f2b 100644 --- a/packages/graphql/lib/src/utilities/platform_html.dart +++ b/packages/graphql/lib/src/utilities/platform_html.dart @@ -1,2 +1,13 @@ -final isHtml = true; -final isIo = false; +import 'package:graphql/src/links/websocket_link/websocket_client.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +Future defaultConnectPlatform( + Uri uri, Iterable? protocols, + {Map? headers}) async { + if (headers != null) { + print("The headers on the web are not supported"); + } + final webSocketChannel = + await WebSocketChannel.connect(uri, protocols: protocols); + return webSocketChannel.forGraphQL(); +} diff --git a/packages/graphql/lib/src/utilities/platform_io.dart b/packages/graphql/lib/src/utilities/platform_io.dart index fa817d2a5..756db7bd8 100644 --- a/packages/graphql/lib/src/utilities/platform_io.dart +++ b/packages/graphql/lib/src/utilities/platform_io.dart @@ -1,2 +1,13 @@ -final isHtml = false; -final isIo = true; +import 'dart:io'; + +import 'package:graphql/src/links/websocket_link/websocket_client.dart'; +import 'package:web_socket_channel/io.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +Future defaultConnectPlatform( + Uri uri, Iterable? protocols, + {Map? headers}) async { + final webSocket = await WebSocket.connect(uri.toString(), + protocols: protocols, headers: headers); + return IOWebSocketChannel(webSocket).forGraphQL(); +} diff --git a/packages/graphql/lib/src/utilities/platform_stub.dart b/packages/graphql/lib/src/utilities/platform_stub.dart deleted file mode 100644 index 51b98b401..000000000 --- a/packages/graphql/lib/src/utilities/platform_stub.dart +++ /dev/null @@ -1,2 +0,0 @@ -final isHtml = false; -final isIo = false; diff --git a/packages/graphql/lib/src/utilities/response.dart b/packages/graphql/lib/src/utilities/response.dart new file mode 100644 index 000000000..02bff9dee --- /dev/null +++ b/packages/graphql/lib/src/utilities/response.dart @@ -0,0 +1,44 @@ +import 'package:graphql/src/core/_base_options.dart'; +import 'package:graphql/src/core/core.dart'; +import 'package:graphql/src/exceptions.dart'; + +QueryResult mapFetchResultToQueryResult( + Response response, + BaseOptions options, { + required QueryResultSource source, +}) { + List? errors; + dynamic data; + + // check if there are errors and apply the error policy if so + // in a nutshell: `ignore` swallows errors, `none` swallows data + if (response.errors != null && response.errors!.isNotEmpty) { + switch (options.errorPolicy) { + case ErrorPolicy.all: + // handle both errors and data + errors = response.errors; + data = response.data; + break; + case ErrorPolicy.ignore: + // ignore errors + data = response.data; + break; + case ErrorPolicy.none: + default: + // TODO not actually sure if apollo even casts graphql errors in `none` mode, + // it's also kind of legacy + errors = response.errors; + break; + } + } else { + data = response.data; + } + + return QueryResult( + data: data, + context: response.context, + source: source, + exception: coalesceErrors(graphqlErrors: errors), + parserFn: options.parserFn, + ); +} diff --git a/packages/graphql/pubspec.yaml b/packages/graphql/pubspec.yaml index 7c36acaaa..e7b65a5af 100644 --- a/packages/graphql/pubspec.yaml +++ b/packages/graphql/pubspec.yaml @@ -1,30 +1,31 @@ name: graphql description: A stand-alone GraphQL client for Dart, bringing all the features from a modern GraphQL client to one easy to use package. -version: 5.0.0 +version: 5.0.2-beta.3 homepage: https://github.com/zino-app/graphql-flutter/tree/master/packages/graphql dependencies: meta: ^1.3.0 path: ^1.8.0 gql: ^0.13.0 - gql_exec: ^0.3.0 - gql_link: ^0.4.0 - gql_http_link: ^0.4.0 + gql_exec: 0.3.0 + gql_link: 0.4.0 + gql_http_link: 0.4.0 gql_transform_link: ^0.2.0 gql_error_link: ^0.2.0 gql_dedupe_link: ^2.0.0 hive: ^2.0.0 - normalize: ^0.5.3 + normalize: ^0.6.0 http: ^0.13.0 collection: ^1.15.0 web_socket_channel: ^2.0.0 stream_channel: ^2.1.0 - rxdart: ^0.26.0 + rxdart: ^0.27.1 uuid: ^3.0.1 dev_dependencies: async: ^2.5.0 - pedantic: ^1.8.0+1 mockito: ^5.0.0 - test: ^1.5.3 + test: ^1.18.2 + coverage: ^1.0.3 http_parser: ^4.0.0 + lints: ^1.0.1 environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/graphql/test/anonymous_operations_test.dart b/packages/graphql/test/anonymous_operations_test.dart index cc5c95879..1c45e555b 100644 --- a/packages/graphql/test/anonymous_operations_test.dart +++ b/packages/graphql/test/anonymous_operations_test.dart @@ -1,5 +1,4 @@ import 'package:gql/language.dart'; -import 'package:gql_exec/gql_exec.dart'; import 'package:test/test.dart'; import 'package:mockito/mockito.dart'; diff --git a/packages/graphql/test/cache/store_test.dart b/packages/graphql/test/cache/store_test.dart index ba0e119e9..de4e6c481 100644 --- a/packages/graphql/test/cache/store_test.dart +++ b/packages/graphql/test/cache/store_test.dart @@ -4,8 +4,6 @@ import 'package:graphql/client.dart'; import 'package:graphql/src/utilities/helpers.dart'; import 'package:test/test.dart'; -import 'package:graphql/src/cache/store.dart'; - void main() { group('InMemoryStore', () { final data = { diff --git a/packages/graphql/test/fetch_policy_test.dart b/packages/graphql/test/fetch_policy_test.dart index cddafffaa..468f2c960 100644 --- a/packages/graphql/test/fetch_policy_test.dart +++ b/packages/graphql/test/fetch_policy_test.dart @@ -1,4 +1,3 @@ -import 'package:gql_exec/gql_exec.dart'; import 'package:test/test.dart'; import 'package:mockito/mockito.dart'; @@ -66,10 +65,10 @@ void main() { group('query', () { // TODO cacheFirst code path: Return result from cache. Only fetch from network if cached result is not available. - // TODO cacheAndNetwork code path: Return result from cache first (if it exists), then return network result once it's available. // TODO cacheOnly code path: Return result from cache if available, fail otherwise. // TODO noCache code path: Return result from network, fail if network call doesn't succeed, don't save to cache. // TODO networkOnly code path: Return result from network, fail if network call doesn't succeed, save to cache. + test('switch to cacheOnly returns cached data', () async { final _options = QueryOptions( fetchPolicy: FetchPolicy.cacheAndNetwork, @@ -118,6 +117,50 @@ void main() { expect(cacheResult.exception, isNull); expect(cacheResult.data, equals(repoData)); }); + test('cacheAndNetwork returns from cache (if exists)', () async { + final _options = QueryOptions( + fetchPolicy: FetchPolicy.cacheAndNetwork, + document: parseString(readRepositories), + variables: { + 'nRepositories': 42, + }, + ); + final repoData = readRepositoryData(withTypenames: true); + + when( + link.request(any), + ).thenAnswer( + (_) => Stream.fromIterable([ + Response(data: repoData), + ]), + ); + + final QueryResult r = await client.query(_options); + + verify( + link.request( + Request( + operation: Operation( + document: parseString(readRepositories), + //operationName: 'ReadRepositories', + ), + variables: { + 'nRepositories': 42, + }, + context: Context(), + ), + ), + ); + + expect(r.exception, isNull); + expect(r.data, equals(repoData)); + expect(r.source, QueryResultSource.network); + + final QueryResult cacheResult = await client.query(_options); + expect(cacheResult.exception, isNull); + expect(cacheResult.data, equals(repoData)); + expect(cacheResult.source, equals(QueryResultSource.cache)); + }); }); }); } diff --git a/packages/graphql/test/graphql_client_test.dart b/packages/graphql/test/graphql_client_test.dart index 1cd623503..aa9c0aa8a 100644 --- a/packages/graphql/test/graphql_client_test.dart +++ b/packages/graphql/test/graphql_client_test.dart @@ -1,5 +1,4 @@ -import 'package:gql_exec/gql_exec.dart'; -import 'package:gql_link/gql_link.dart'; +import 'package:graphql/src/core/result_parser.dart'; import 'package:test/test.dart'; import 'package:mockito/mockito.dart'; @@ -43,7 +42,7 @@ void main() { } } '''; - readRepositoryData({ + Map readRepositoryData({ bool withTypenames = true, bool withIds = true, bool viewerHasStarred = false, @@ -154,6 +153,145 @@ void main() { equals('bar'), ); }); + test('successful response with parser', () async { + final ResultParserFn> parserFn = (data) { + return data['viewer']['repositories']['nodes'] + .map((node) => node['name'] as String) + .toList(); + }; + final _options = QueryOptions( + document: parseString(readRepositories), + variables: { + 'nRepositories': 42, + }, + parserFn: parserFn, + ); + final repoData = readRepositoryData(withTypenames: true); + + when( + link.request(any), + ).thenAnswer( + (_) => Stream.fromIterable([ + Response( + data: repoData, + context: Context().withEntry( + HttpLinkResponseContext( + statusCode: 200, + headers: {'foo': 'bar'}, + ), + ), + ), + ]), + ); + + final QueryResult> r = await client.query(_options); + + verify( + link.request( + Request( + operation: Operation( + document: parseString(readRepositories), + //operationName: 'ReadRepositories', + ), + variables: { + 'nRepositories': 42, + }, + context: Context(), + ), + ), + ); + + expect(r.exception, isNull); + expect(r.data, equals(repoData)); + + List? parsedData = r.parsedData; + expect( + parsedData, + equals([ + 'pq', + 'go-evercookie', + 'watchbot', + ])); + + expect( + r.context.entry()!.statusCode, + equals(200), + ); + expect( + r.context.entry()!.headers['foo'], + equals('bar'), + ); + }); + test('successful fetch-more with parser', () async { + final ResultParserFn> parserFn = (data) { + return data['viewer']['repositories']['nodes'] + .map((node) => node['name'] as String) + .toList(); + }; + final _options = QueryOptions( + document: parseString(readRepositories), + variables: { + 'nRepositories': 42, + }, + parserFn: parserFn, + ); + final repoData = readRepositoryData(withTypenames: true); + + when( + link.request(any), + ).thenAnswer( + (_) => Stream.fromIterable([ + Response( + data: repoData, + context: Context().withEntry( + HttpLinkResponseContext( + statusCode: 200, + headers: {'foo': 'bar'}, + ), + ), + ), + ]), + ); + + final QueryResult> r1 = await client.query(_options); + expect(r1.exception, isNull); + expect(r1.data, equals(repoData)); + expect( + r1.parsedData, + equals([ + 'pq', + 'go-evercookie', + 'watchbot', + ])); + final QueryResult> r2 = await client.fetchMore( + FetchMoreOptions( + updateQuery: (d1, d2) => ({ + 'viewer': { + 'repositories': { + 'nodes': [ + ...d1!['viewer']['repositories']['nodes'], + ...d2!['viewer']['repositories']['nodes'], + ] + } + } + }), + ), + previousResult: r1, + originalOptions: _options, + ); + + expect(r2.exception, isNull); + expect( + r2.parsedData, + equals([ + 'pq', + 'go-evercookie', + 'watchbot', + 'pq', + 'go-evercookie', + 'watchbot', + ])); + }); test('successful response without normalization', () async { final readUnidentifiedRepositories = parseString(r''' @@ -429,6 +567,54 @@ void main() { response.data!['action']['starrable']['viewerHasStarred'] as bool?; expect(viewerHasStarred, true); }); + test('successful mutation with parser', () async { + final ResultParserFn resultParser = + (data) => data['action']['starrable']['viewerHasStarred'] as bool; + final MutationOptions _options = MutationOptions( + document: parseString(addStar), + parserFn: resultParser, + ); + + when( + link.request(any), + ).thenAnswer( + (_) => Stream.fromIterable( + [ + Response( + data: { + 'action': { + 'starrable': { + 'viewerHasStarred': true, + }, + }, + }, + ), + ], + ), + ); + + final QueryResult response = await client.mutate(_options); + + verify( + link.request( + Request( + operation: Operation( + document: parseString(addStar), + //operationName: 'AddStar', + ), + variables: {}, + context: Context(), + ), + ), + ); + final bool parsedResult = response.parsedData; + expect(parsedResult, isTrue); + expect(response.exception, isNull); + expect(response.data, isNotNull); + final bool? viewerHasStarred = + response.data!['action']['starrable']['viewerHasStarred'] as bool?; + expect(viewerHasStarred, true); + }); test('successful mutation through watchQuery', () async { final _options = MutationOptions( @@ -541,6 +727,139 @@ void main() { ), ); }); + test('broadcasts', () async { + const initialName = 'initial'; + const firstUpdateName = 'first'; + const secondUpdateName = 'second'; + final initialQueryResponse = Response( + data: { + 'single': { + 'id': '1', + '__typename': 'Item', + 'name': initialName, + }, + }, + ); + when( + link.request(any), + ).thenAnswer( + (_) => Stream.fromIterable( + [initialQueryResponse], + ), + ); + + final ObservableQuery observable = client.watchQuery( + WatchQueryOptions( + document: parseString(readSingle), + eagerlyFetchResults: true, + variables: {'id': '1'}, + ), + ); + final responses = [ + {'id': '1', 'name': firstUpdateName, '__typename': 'Item'}, + {'id': '1', 'name': secondUpdateName, '__typename': 'Item'}, + ].map((item) => Response(data: {'item': item})); + when( + link.request(any), + ).thenAnswer( + (_) => Stream.fromIterable(responses), + ); + final stream = client.subscribe( + SubscriptionOptions( + document: parseString( + r''' + subscription { + item { + id + name + } + } + ''', + ), + ), + ); + expect( + stream, + emitsInOrder( + [ + isA().having( + (result) => result.data!['item']['name'], + 'name', + firstUpdateName, + ), + isA().having( + (result) => result.data!['item']['name'], + 'name', + secondUpdateName, + ) + ], + ), + ); + expect( + observable.stream, + emitsInOrder( + [ + isA().having( + (result) => result.data, + 'name', + null, + ), + isA().having( + (result) => result.data!['single']['name'], + 'name', + initialName, + ), + isA().having( + (result) => result.data!['single']['name'], + 'name', + firstUpdateName, + ), + isA().having( + (result) => result.data!['single']['name'], + 'name', + secondUpdateName, + ) + ], + ), + ); + }); + test('parses results', () async { + final responses = [ + {'id': '1', 'name': 'first', '__typename': 'Item'}, + {'id': '2', 'name': 'second', '__typename': 'Item'}, + ].map((item) => Response(data: {'item': item})); + when(link.request(any)) + .thenAnswer((_) => Stream.fromIterable(responses)); + + final ResultParserFn parserFn = + (data) => data['item']['name'] as String; + ; + + final stream = client.subscribe( + SubscriptionOptions( + parserFn: parserFn, + document: parseString( + r''' + subscription { + item { + id + name + } + } + ''', + ), + ), + ); + + expect( + stream, + emitsInOrder(['first', 'second'] + .map((e) => isA>().having((result) { + final String? parsed = result.parsedData; + return parsed; + }, "Parsed item", e))), + ); + }); test('wraps stream exceptions', () async { final ex = ServerException( diff --git a/packages/graphql/test/mock_server/ws_echo_server.dart b/packages/graphql/test/mock_server/ws_echo_server.dart new file mode 100644 index 000000000..45c5360da --- /dev/null +++ b/packages/graphql/test/mock_server/ws_echo_server.dart @@ -0,0 +1,26 @@ +/// Web Socket echo server +/// to run the test and cover the web socket test +/// +/// author: https://github.com/vincenzopalazzo +import 'dart:io'; + +const String forceDisconnectCommand = '___force_disconnect___'; + +/// Main function to create and run the echo server over the web socket. +Future runWebSocketServer( + {String host = "127.0.0.1", int port = 5600}) async { + HttpServer server = await HttpServer.bind(host, port); + server.transform(WebSocketTransformer()).listen(onWebSocketData); + return "ws://$host:$port"; +} + +/// Handle event received on server. +void onWebSocketData(WebSocket client) { + client.listen((data) async { + if (data != null && data.toString().contains(forceDisconnectCommand)) { + client.close(WebSocketStatus.normalClosure, 'shutting down'); + } else { + client.add(data); + } + }); +} diff --git a/packages/graphql/test/query_options_test.dart b/packages/graphql/test/query_options_test.dart index 67884b54a..a6cb1641e 100644 --- a/packages/graphql/test/query_options_test.dart +++ b/packages/graphql/test/query_options_test.dart @@ -5,6 +5,29 @@ import 'package:test/test.dart'; void main() { group('query options', () { + group('equality', () { + test('compares content', () { + final fn = (_d) => null; + final t1 = QueryOptions( + variables: {'foo': 'bar'}, + document: parseString('query { bar }'), + parserFn: fn, + ); + final t2 = QueryOptions( + variables: {'foo': 'bar'}, + document: parseString('query { bar }'), + parserFn: fn, + ); + final t3 = QueryOptions( + document: parseString('query { barz }'), + parserFn: fn, + ); + expect(t1, equals(t2)); + expect(t1.hashCode, equals(t2.hashCode)); + expect(t1, isNot(equals(t3))); + expect(t1.hashCode, isNot(equals(t3.hashCode))); + }); + }); group('type getters', () { test('on QueryOptions', () { final options = QueryOptions( diff --git a/packages/graphql/test/websocket_test.dart b/packages/graphql/test/websocket_test.dart index bd6f9fdc3..72faa05a3 100644 --- a/packages/graphql/test/websocket_test.dart +++ b/packages/graphql/test/websocket_test.dart @@ -1,103 +1,38 @@ import 'dart:async'; +import 'dart:io'; -import 'package:async/async.dart'; -import 'package:rxdart/subjects.dart'; -import 'package:stream_channel/src/stream_channel_transformer.dart'; -import 'package:stream_channel/stream_channel.dart'; import 'package:test/test.dart'; import 'dart:convert'; import 'dart:typed_data'; import 'package:gql/language.dart'; import 'package:graphql/client.dart'; -import 'package:graphql/src/links/websocket_link/websocket_client.dart'; -import 'package:graphql/src/links/websocket_link/websocket_messages.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; import './helpers.dart'; - -class EchoSink extends DelegatingStreamSink implements WebSocketSink { - final StreamSink sink; - - EchoSink(StreamSink sink) - : this.sink = sink, - super(sink); - - @override - Future close([int? closeCode, String? closeReason]) { - return super.close(); - } -} - -class EchoSocket implements WebSocketChannel { - final StreamController controller; - - EchoSocket.connect(this.controller) : sink = EchoSink(controller.sink); - - @override - Stream get stream => controller.stream; - - @override - final WebSocketSink sink; - - @override - StreamChannel cast() => throw UnimplementedError(); - - @override - StreamChannel changeSink( - StreamSink Function(StreamSink p1) change, - ) => - throw UnimplementedError(); - - @override - StreamChannel changeStream( - Stream Function(Stream p1) change, - ) => - throw UnimplementedError(); - - @override - int get closeCode => throw UnimplementedError(); - - @override - String get closeReason => throw UnimplementedError(); - - @override - void pipe(StreamChannel other) {} - - @override - String get protocol => throw UnimplementedError(); - - @override - StreamChannel transform( - StreamChannelTransformer transformer, - ) => - throw UnimplementedError(); - - @override - StreamChannel transformSink( - StreamSinkTransformer transformer, - ) => - throw UnimplementedError(); - - @override - StreamChannel transformStream( - StreamTransformer transformer, - ) => - throw UnimplementedError(); -} - -SocketClient getTestClient([StreamController? controller]) => SocketClient( - 'ws://echo.websocket.org', - connect: (_, __) => EchoSocket.connect(controller ?? BehaviorSubject()), +import './mock_server/ws_echo_server.dart'; +import 'mock_server/ws_echo_server.dart'; + +SocketClient getTestClient( + {required String wsUrl, + StreamController? controller, + bool autoReconnect = true, + Map? customHeaders, + Duration delayBetweenReconnectionAttempts = + const Duration(milliseconds: 1)}) => + SocketClient( + wsUrl, config: SocketClientConfig( - delayBetweenReconnectionAttempts: Duration(milliseconds: 1), + autoReconnect: autoReconnect, + headers: customHeaders, + delayBetweenReconnectionAttempts: delayBetweenReconnectionAttempts, ), randomBytesForUuid: Uint8List.fromList( [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], ), ); -void main() { +Future main() async { + String wsUrl = await runWebSocketServer(); group('InitOperation', () { test('null payload', () { // ignore: deprecated_member_use_from_same_package @@ -140,7 +75,7 @@ void main() { r'}'; setUp(overridePrint((log) { controller = StreamController(sync: true); - socketClient = getTestClient(controller); + socketClient = getTestClient(controller: controller, wsUrl: wsUrl); })); tearDown(overridePrint( (log) => socketClient.dispose(), @@ -156,6 +91,47 @@ void main() { ), ); }); + test('disconnect via dispose', () async { + // First wait for connection to complete + await expectLater( + socketClient.connectionState.asBroadcastStream(), + emitsInOrder( + [ + SocketConnectionState.connecting, + SocketConnectionState.connected, + ], + ), + ); + + // We need to begin waiting on the connectionState + // before we issue the command to disconnect; otherwise + // it can reconnect so fast that it will be reconnected + // by the time that the expectLater check is initiated. + await overridePrint((_) async { + Timer(const Duration(milliseconds: 20), () async { + await socketClient.dispose(); + }); + })(); + // The connectionState BehaviorController emits the current state + // to any new listener, so we expect it to start in the connected + // state and transition to notConnected because of dispose. + await expectLater( + socketClient.connectionState, + emitsInOrder([ + SocketConnectionState.connected, + SocketConnectionState.notConnected, + ]), + ); + + // Have to wait for socket close to be fully processed after we reach + // the notConnected state, including updating channel with close code. + await Future.delayed(const Duration(milliseconds: 20)); + + // The websocket should be in a fully closed state at this point, + // we should have a confirmed close code in the channel. + expect(socketClient.socketChannel, isNotNull); + expect(socketClient.socketChannel!.closeCode, isNotNull); + }); test('subscription data', () async { final payload = Request( operation: Operation(document: parseString('subscription {}')), @@ -214,7 +190,9 @@ void main() { ]), ); - socketClient.onConnectionLost(); + await overridePrint((_) async { + socketClient.onConnectionLost(); + })(); await expectLater( socketClient.connectionState, @@ -242,6 +220,73 @@ void main() { })); }); + await expectLater( + subscriptionDataStream, + emits( + // todo should ids be included in response context? probably '01020304-0506-4708-890a-0b0c0d0e0f10' + Response( + data: {'foo': 'bar'}, + errors: [ + GraphQLError(message: 'error and data can coexist'), + ], + context: Context().withEntry(ResponseExtensions(null)), + ), + ), + ); + }); + test('resubscribe after server disconnect', () async { + final payload = Request( + operation: Operation(document: gql('subscription {}')), + ); + final waitForConnection = true; + final subscriptionDataStream = + socketClient.subscribe(payload, waitForConnection); + + await expectLater( + socketClient.connectionState, + emitsInOrder([ + SocketConnectionState.connecting, + SocketConnectionState.connected, + ]), + ); + + // We need to begin waiting on the connectionState + // before we issue the command to disconnect; otherwise + // it can reconnect so fast that it will be reconnected + // by the time that the expectLater check is initiated. + Timer(const Duration(milliseconds: 20), () async { + socketClient.socketChannel!.sink.add(forceDisconnectCommand); + }); + // The connectionState BehaviorController emits the current state + // to any new listener, so we expect it to start in the connected + // state, transition to notConnected, and then reconnect after that. + await expectLater( + socketClient.connectionState, + emitsInOrder([ + SocketConnectionState.connected, + SocketConnectionState.notConnected, + SocketConnectionState.connecting, + SocketConnectionState.connected, + ]), + ); + + // ignore: unawaited_futures + socketClient.socketChannel!.stream + .where((message) => message == expectedMessage) + .first + .then((_) { + socketClient.socketChannel!.sink.add(jsonEncode({ + 'type': 'data', + 'id': '01020304-0506-4708-890a-0b0c0d0e0f10', + 'payload': { + 'data': {'foo': 'bar'}, + 'errors': [ + {'message': 'error and data can coexist'} + ] + } + })); + }); + await expectLater( subscriptionDataStream, emits( @@ -258,14 +303,57 @@ void main() { }); }, tags: "integration"); + group('SocketClient without autoReconnect', () { + late SocketClient socketClient; + StreamController controller; + setUp(overridePrint((log) { + controller = StreamController(sync: true); + socketClient = getTestClient( + controller: controller, wsUrl: wsUrl, autoReconnect: false); + })); + tearDown(overridePrint( + (log) => socketClient.dispose(), + )); + test('server disconnect', () async { + final payload = Request( + operation: Operation(document: gql('subscription {}')), + ); + final waitForConnection = true; + socketClient.subscribe(payload, waitForConnection); + + await expectLater( + socketClient.connectionState, + emitsInOrder([ + SocketConnectionState.connecting, + SocketConnectionState.connected, + ]), + ); + + Timer(const Duration(milliseconds: 20), () async { + socketClient.socketChannel!.sink.add(forceDisconnectCommand); + }); + // Same strategy as elsewhere, start expecting the state on the + // stream before the disconnect actually happens... + await expectLater( + socketClient.connectionState, + emitsInOrder([ + SocketConnectionState.connected, + SocketConnectionState.notConnected, + ]), + ); + + expect( + socketClient.socketChannel!.closeCode, WebSocketStatus.normalClosure); + }); + }, tags: "integration"); + group('SocketClient with const payload', () { late SocketClient socketClient; const initPayload = {'token': 'mytoken'}; setUp(overridePrint((log) { socketClient = SocketClient( - 'ws://echo.websocket.org', - connect: (_, __) => EchoSocket.connect(BehaviorSubject()), + wsUrl, config: SocketClientConfig(initialPayload: () => initPayload), ); })); @@ -296,8 +384,7 @@ void main() { setUp(overridePrint((log) { socketClient = SocketClient( - 'ws://echo.websocket.org', - connect: (_, __) => EchoSocket.connect(BehaviorSubject()), + wsUrl, config: SocketClientConfig( initialPayload: () async { await Future.delayed(Duration(seconds: 3)); @@ -323,5 +410,22 @@ void main() { emits(initPayload), ); }); + + /* + FIXME: Testing the correct header in the request + group('SocketClient with custom headers with const payload', () { + const customHeaders = {'myHeader': 'myHeader'}; + + setUp(overridePrint((log) { + socketClient = getTestClient(wsUrl: wsUrl, customHeaders: customHeaders); + })); + + test('check header', () async { + await socketClient.connectionState + .where((state) => state == SocketConnectionState.notConnected) + .first; + }); + }); + */ }); } diff --git a/packages/graphql_flutter/CHANGELOG.md b/packages/graphql_flutter/CHANGELOG.md index f10db8000..e1db65de5 100644 --- a/packages/graphql_flutter/CHANGELOG.md +++ b/packages/graphql_flutter/CHANGELOG.md @@ -1,3 +1,101 @@ +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-09) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-08) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-07) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-06) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-05) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-04) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-03) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-02) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-12-01) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-11-30) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-11-29) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-11-28) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-11-27) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + +## [5.0.1-beta.1](https://github.com/zino-app/graphql-flutter/compare/v5.0.0...v5.0.1-beta.1) (2021-11-26) + + +### Performance Improvements + +* open Hive boxes concurrently ([33ea16e](https://github.com/zino-app/graphql-flutter/commit/33ea16e700375eb5e874ae56591c03e1f11c4a4c)) + # [5.0.0](https://github.com/zino-app/graphql-flutter/compare/v4.0.1...v5.0.0) (2021-06-07) diff --git a/packages/graphql_flutter/README.md b/packages/graphql_flutter/README.md index bb7f77317..8b1a28959 100644 --- a/packages/graphql_flutter/README.md +++ b/packages/graphql_flutter/README.md @@ -27,6 +27,8 @@ This guide is mostly focused on setup, widgets, and flutter-specific considerati - [Optimism](#optimism) - [Subscriptions](#subscriptions) - [GraphQL Consumer](#graphql-consumer) + - [Other hooks](#other-hooks) + - [Code generation](#code-generation) **Useful sections in the [`graphql` README](../graphql/README.md):** @@ -49,9 +51,8 @@ This guide is mostly focused on setup, widgets, and flutter-specific considerati First, depend on this package: -```yaml -dependencies: - graphql_flutter: ^4.0.0-beta +```console +$ flutter pub add graphql_flutter ``` And then import it inside your dart code: @@ -61,14 +62,13 @@ import 'package:graphql_flutter/graphql_flutter.dart'; ``` ## Migration Guide - Find the migration from version 3 to version 4 [here](./../../changelog-v3-v4.md). ## Usage To connect to a GraphQL Server, we first need to create a `GraphQLClient`. A `GraphQLClient` requires both a `cache` and a `link` to be initialized. -In our example below, we will be using the Github Public API. we are going to use `HttpLink` which we will concatenate with `AuthLink` so as to attach our github access token. For the cache, we are going to use `GraphQLCache`. +In our example below, we will be using the Github Public API. we are going to use `HttpLink` which we will concatenate with `AuthLink` so as to attach our [github access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token). For the cache, we are going to use `GraphQLCache`. ```dart ... @@ -97,7 +97,7 @@ void main() async { GraphQLClient( link: link, // The default store is the InMemoryStore, which does NOT persist to disk - store: GraphQLCache(store: HiveStore()), + cache: GraphQLCache(store: HiveStore()), ), ); @@ -157,34 +157,79 @@ Query( variables: { 'nRepositories': 50, }, - pollInterval: Duration(seconds: 10), + pollInterval: const Duration(seconds: 10), ), // Just like in apollo refetch() could be used to manually trigger a refetch // while fetchMore() can be used for pagination purpose - builder: (QueryResult result, { VoidCallback refetch, FetchMore fetchMore }) { + builder: (QueryResult result, { VoidCallback? refetch, FetchMore? fetchMore }) { if (result.hasException) { return Text(result.exception.toString()); } if (result.isLoading) { - return Text('Loading'); + return const Text('Loading'); } - // it can be either Map or List - List repositories = result.data['viewer']['repositories']['nodes']; + List? repositories = result.data?['viewer']?['repositories']?['nodes']; + + if (repositories == null) { + return const Text('No repositories'); + } return ListView.builder( itemCount: repositories.length, itemBuilder: (context, index) { final repository = repositories[index]; - return Text(repository['name']); + return Text(repository['name'] ?? ''); }); }, ); // ... ``` +or if you prefer to use [flutter-hooks](https://pub.dev/packages/flutter_hooks), you can write the above as: + + +```dart +// ... +final readRespositoriesResult = useQuery( + QueryOptions( + document: gql(readRepositories), // this is the query string you just created + variables: { + 'nRepositories': 50, + }, + pollInterval: const Duration(seconds: 10), + ), +); +final result = readRespositoriesResult.result; + +if (result.hasException) { + return Text(result.exception.toString()); +} + +if (result.isLoading) { + return const Text('Loading'); +} + +List? repositories = result.data?['viewer']?['repositories']?['nodes']; + +if (repositories == null) { + return const Text('No repositories'); +} + +return ListView.builder( + itemCount: repositories.length, + itemBuilder: (context, index) { + final repository = repositories[index]; + + return Text(repository['name'] ?? ''); +}); +// ... + + +``` + #### Fetch More (Pagination) You can use `fetchMore()` function inside `Query` Builder to perform pagination. The `fetchMore()` function allows you to run an entirely new GraphQL operation and merge the new results with the original results. On top of that, you can re-use aspects of the Original query i.e. the Query or some of the Variables. @@ -288,6 +333,36 @@ Mutation( ... ``` +The corresponding hook is + +```dart + +// ... + +final addStarMutation = useMutation( + MutationOptions( + document: gql(addStar), // this is the mutation string you just created + // you can update the cache based on results + update: (GraphQLDataProxy cache, QueryResult result) { + return cache; + }, + // or do something with the result.data on completion + onCompleted: (dynamic resultData) { + print(resultData); + }, + ), +); +return FloatingActionButton( + onPressed: () => addStarMutation.runMutation({ + 'starrableId': , + }), + tooltip: 'Star', + child: Icon(Icons.star), +); + +// ... +``` + `graphql` also provides [file upload](../graphql/README.md#graphql-upload) support as well. @@ -437,6 +512,63 @@ class _MyHomePageState extends State { } ``` +the corresponding implementation with hooks is: + + +```dart +final subscriptionDocument = gql( + r''' + subscription reviewAdded { + reviewAdded { + stars, commentary, episode + } + } + ''', +); + +class MyHomePage extends HooksWidget { + @override + Widget build(BuildContext context) { + final result = useSubscription( + SubscriptionOptions( + document: subscriptionDocument, + ), + ); + + Widget child; + if (result.hasException) { + child = Text(result.exception.toString()); + } else if (result.isLoading) { + child = Center( + child: const CircularProgressIndicator(), + ); + } else { + child = ResultAccumulator.appendUniqueEntries( + latest: result.data, + builder: (context, {results}) => DisplayReviews( + reviews: results.reversed.toList(), + ), + ); + } + return Scaffold( + body: Center(child: child) + ); + } +} +``` + +### Other hooks + +Besides `useMutation`, `useQuery`, and `useSubscription`, this package contains the following hooks: + +```dart +final client = useGraphQLClient(); // Fetch the current client +final observableQuery = useWatchQuery(WatchQueryOptions(...)); // Watch a query +final mutationObservableQuery = useWatchMutation(WatchQueryOptions(...)); // Watch a query +``` + + + ### GraphQL Consumer If you want to use the `client` directly, say for some its @@ -459,16 +591,62 @@ You can use `GraphQLConsumer` to grab it from any `context` descended from a `Gr ... ``` -[build-status-badge]: https://img.shields.io/circleci/build/github/zino-app/graphql-flutter.svg?style=flat-square -[build-status-link]: https://circleci.com/gh/zino-app/graphql-flutter -[coverage-badge]: https://img.shields.io/codecov/c/github/zino-app/graphql-flutter.svg?style=flat-square -[coverage-link]: https://codecov.io/gh/zino-app/graphql-flutter +## Code generation + +This package does not support code-generation out of the box, but [graphql_codegen](https://pub.dev/packages/graphql_codegen) does! + +This package generate hooks and options which takes away the struggle of serialization and gives you confidence through type-safety. + +For example, by creating the `.graphql` file + +```graphql + query ReadRepositories($nRepositories: Int!) { + viewer { + repositories(last: $nRepositories) { + nodes { + id + name + } + } + } + } +``` + +after building, you'll be able to query this in your code through the hook: + +```dart +final queryResult = useQueryReadRepositories( + OptionsQueryReadRepositories( + variables: VariablesQueryReadRepositories( + nRepositories: 10 + ) + ) +); +if (queryResult.result.hasException) { + return Text(result.exception.toString()); +} +if (queryResult.result.isLoading) { + return Text(text: "LOADING"); +} +final data = queryResult.result.parsedData; + +return Column( + children: data?.viewer.repositores.nodes.map((node) => Text(text: node.name)); +); +``` + + + +[build-status-badge]: https://img.shields.io/github/workflow/status/zino-hofmann/graphql-flutter/graphql-flutter%20Tests%20case?style=flat-square +[build-status-link]: https://github.com/zino-hofmann/graphql-flutter/actions +[coverage-badge]: https://img.shields.io/codecov/c/github/zino-hofmann/graphql-flutter/beta?style=flat-square +[coverage-link]: https://app.codecov.io/gh/zino-hofmann/graphql-flutter [version-badge]: https://img.shields.io/pub/v/graphql_flutter.svg?style=flat-square [package-link]: https://pub.dartlang.org/packages/graphql_flutter [license-badge]: https://img.shields.io/github/license/zino-app/graphql-flutter.svg?style=flat-square [license-link]: https://github.com/zino-app/graphql-flutter/blob/master/LICENSE [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square -[prs-link]: http://makeapullrequest.com +[prs-link]: https://makeapullrequest.com [github-watch-badge]: https://img.shields.io/github/watchers/zino-app/graphql-flutter.svg?style=flat-square&logo=github&logoColor=ffffff [github-watch-link]: https://github.com/zino-app/graphql-flutter/watchers [github-star-badge]: https://img.shields.io/github/stars/zino-app/graphql-flutter.svg?style=flat-square&logo=github&logoColor=ffffff diff --git a/packages/graphql_flutter/example/README.md b/packages/graphql_flutter/example/README.md index fb200736d..3608e1745 100644 --- a/packages/graphql_flutter/example/README.md +++ b/packages/graphql_flutter/example/README.md @@ -1,8 +1,7 @@ -# Example App +# graphql_flutter example -A new Flutter project. +A Github API wrapper example where you need to specify you token in the `local.dart` file ## Getting Started -For help getting started with Flutter, view our online -[documentation](https://flutter.io/). +Just run `flutter pub get` and run your flutter app diff --git a/packages/graphql_flutter/example/android/.gitignore b/packages/graphql_flutter/example/android/.gitignore index 65b7315af..6f568019d 100644 --- a/packages/graphql_flutter/example/android/.gitignore +++ b/packages/graphql_flutter/example/android/.gitignore @@ -1,10 +1,13 @@ -*.iml -*.class -.gradle +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat /local.properties -/.idea/workspace.xml -/.idea/libraries -.DS_Store -/build -/captures GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/packages/graphql_flutter/example/android/app/build.gradle b/packages/graphql_flutter/example/android/app/build.gradle index 0d8279847..69f8d594f 100644 --- a/packages/graphql_flutter/example/android/app/build.gradle +++ b/packages/graphql_flutter/example/android/app/build.gradle @@ -11,24 +11,43 @@ if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 28 + compileSdkVersion flutter.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } - lintOptions { - disable 'InvalidPackage' + sourceSets { + main.java.srcDirs += 'src/main/kotlin' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.app" - minSdkVersion 16 - targetSdkVersion 27 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + applicationId "com.example.demo_migaration" + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName } buildTypes { @@ -38,11 +57,6 @@ android { signingConfig signingConfigs.debug } } - - // Temporary fix until alpha10 - packagingOptions { - exclude 'META-INF/proguard/androidx-annotations.pro' - } } flutter { @@ -50,7 +64,5 @@ flutter { } dependencies { - testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.1' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/examples/flutter_bloc/android/app/src/debug/AndroidManifest.xml b/packages/graphql_flutter/example/android/app/src/debug/AndroidManifest.xml similarity index 83% rename from examples/flutter_bloc/android/app/src/debug/AndroidManifest.xml rename to packages/graphql_flutter/example/android/app/src/debug/AndroidManifest.xml index 1321dfaba..b15a3d5fb 100644 --- a/examples/flutter_bloc/android/app/src/debug/AndroidManifest.xml +++ b/packages/graphql_flutter/example/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.example.demo_migaration"> diff --git a/packages/graphql_flutter/example/android/app/src/main/AndroidManifest.xml b/packages/graphql_flutter/example/android/app/src/main/AndroidManifest.xml index 2476f78b9..f153f1bf5 100644 --- a/packages/graphql_flutter/example/android/app/src/main/AndroidManifest.xml +++ b/packages/graphql_flutter/example/android/app/src/main/AndroidManifest.xml @@ -1,39 +1,34 @@ - - - - - - + - + + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" + /> + + diff --git a/packages/graphql_flutter/example/android/app/src/main/java/com/example/app/MainActivity.java b/packages/graphql_flutter/example/android/app/src/main/java/com/example/app/MainActivity.java deleted file mode 100644 index a66788e1a..000000000 --- a/packages/graphql_flutter/example/android/app/src/main/java/com/example/app/MainActivity.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.app; - -import android.os.Bundle; - -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; - -public class MainActivity extends FlutterActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); - } -} diff --git a/packages/graphql_flutter/example/android/app/src/main/kotlin/com/example/demo_migaration/MainActivity.kt b/packages/graphql_flutter/example/android/app/src/main/kotlin/com/example/demo_migaration/MainActivity.kt new file mode 100644 index 000000000..762f551b5 --- /dev/null +++ b/packages/graphql_flutter/example/android/app/src/main/kotlin/com/example/demo_migaration/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.demo_migaration + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/examples/flutter_bloc/android/app/src/main/res/drawable/launch_background.xml b/packages/graphql_flutter/example/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 86% rename from examples/flutter_bloc/android/app/src/main/res/drawable/launch_background.xml rename to packages/graphql_flutter/example/android/app/src/main/res/drawable-v21/launch_background.xml index 304732f88..f74085f3f 100644 --- a/examples/flutter_bloc/android/app/src/main/res/drawable/launch_background.xml +++ b/packages/graphql_flutter/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -1,7 +1,7 @@ - + + + + + diff --git a/packages/graphql_flutter/example/android/app/src/main/res/values/styles.xml b/packages/graphql_flutter/example/android/app/src/main/res/values/styles.xml index 00fa4417c..d460d1e92 100644 --- a/packages/graphql_flutter/example/android/app/src/main/res/values/styles.xml +++ b/packages/graphql_flutter/example/android/app/src/main/res/values/styles.xml @@ -1,8 +1,18 @@ - + + diff --git a/examples/flutter_bloc/android/app/src/profile/AndroidManifest.xml b/packages/graphql_flutter/example/android/app/src/profile/AndroidManifest.xml similarity index 83% rename from examples/flutter_bloc/android/app/src/profile/AndroidManifest.xml rename to packages/graphql_flutter/example/android/app/src/profile/AndroidManifest.xml index 1321dfaba..b15a3d5fb 100644 --- a/examples/flutter_bloc/android/app/src/profile/AndroidManifest.xml +++ b/packages/graphql_flutter/example/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.example.demo_migaration"> diff --git a/packages/graphql_flutter/example/android/build.gradle b/packages/graphql_flutter/example/android/build.gradle index 23d41d039..24047dce5 100644 --- a/packages/graphql_flutter/example/android/build.gradle +++ b/packages/graphql_flutter/example/android/build.gradle @@ -1,18 +1,20 @@ buildscript { + ext.kotlin_version = '1.3.50' repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.3' + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/packages/graphql_flutter/example/android/gradle.properties b/packages/graphql_flutter/example/android/gradle.properties index 5c4a2a4c2..94adc3a3f 100644 --- a/packages/graphql_flutter/example/android/gradle.properties +++ b/packages/graphql_flutter/example/android/gradle.properties @@ -1,3 +1,3 @@ org.gradle.jvmargs=-Xmx1536M -android.enableR8=true +android.useAndroidX=true android.enableJetifier=true diff --git a/packages/graphql_flutter/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/graphql_flutter/example/android/gradle/wrapper/gradle-wrapper.properties index 1401a2e7d..bc6a58afd 100644 --- a/packages/graphql_flutter/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/graphql_flutter/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-rc-1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/packages/graphql_flutter/example/android/settings.gradle b/packages/graphql_flutter/example/android/settings.gradle index 5a2f14fb1..44e62bcf0 100644 --- a/packages/graphql_flutter/example/android/settings.gradle +++ b/packages/graphql_flutter/example/android/settings.gradle @@ -1,15 +1,11 @@ include ':app' -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } -} +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory -} +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/graphql_flutter/example/ios/.gitignore b/packages/graphql_flutter/example/ios/.gitignore index 79cc4da80..7a7f9873a 100644 --- a/packages/graphql_flutter/example/ios/.gitignore +++ b/packages/graphql_flutter/example/ios/.gitignore @@ -1,45 +1,34 @@ -.idea/ -.vagrant/ -.sconsign.dblite -.svn/ - -.DS_Store -*.swp -profile - -DerivedData/ -build/ -GeneratedPluginRegistrant.h -GeneratedPluginRegistrant.m - -.generated/ - -*.pbxuser +**/dgph *.mode1v3 *.mode2v3 +*.moved-aside +*.pbxuser *.perspectivev3 - -!default.pbxuser +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. !default.mode1v3 !default.mode2v3 +!default.pbxuser !default.perspectivev3 - -xcuserdata - -*.moved-aside - -*.pyc -*sync/ -Icon? -.tags* - -/Flutter/app.flx -/Flutter/app.zip -/Flutter/flutter_assets/ -/Flutter/App.framework -/Flutter/Flutter.framework -/Flutter/Generated.xcconfig -/ServiceDefinitions.json - -Pods/ -.symlinks/ diff --git a/packages/graphql_flutter/example/ios/Flutter/AppFrameworkInfo.plist b/packages/graphql_flutter/example/ios/Flutter/AppFrameworkInfo.plist index 9367d483e..8d4492f97 100644 --- a/packages/graphql_flutter/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/graphql_flutter/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 9.0 diff --git a/packages/graphql_flutter/example/ios/Flutter/Debug.xcconfig b/packages/graphql_flutter/example/ios/Flutter/Debug.xcconfig index e8efba114..ec97fc6f3 100644 --- a/packages/graphql_flutter/example/ios/Flutter/Debug.xcconfig +++ b/packages/graphql_flutter/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/graphql_flutter/example/ios/Flutter/Release.xcconfig b/packages/graphql_flutter/example/ios/Flutter/Release.xcconfig index 399e9340e..c4855bfe2 100644 --- a/packages/graphql_flutter/example/ios/Flutter/Release.xcconfig +++ b/packages/graphql_flutter/example/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/graphql_flutter/example/ios/Podfile b/packages/graphql_flutter/example/ios/Podfile index f7d6a5e68..1e8c3c90a 100644 --- a/packages/graphql_flutter/example/ios/Podfile +++ b/packages/graphql_flutter/example/ios/Podfile @@ -28,6 +28,9 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do + use_frameworks! + use_modular_headers! + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end diff --git a/packages/graphql_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/graphql_flutter/example/ios/Runner.xcodeproj/project.pbxproj index bf430a69e..9e093b833 100644 --- a/packages/graphql_flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/graphql_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,16 +3,13 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 85BD965404134B0319457AD8 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9CD12FDAA2BE6D4AEA7ACE /* libPods-Runner.a */; }; - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; - 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -35,20 +32,16 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 4A9CD12FDAA2BE6D4AEA7ACE /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 9235FC581E2F702C430A0573 /* 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 = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - C9CB04F761683177D49AF7FB /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,21 +49,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 85BD965404134B0319457AD8 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 50488B4156A6CC29CC303635 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 4A9CD12FDAA2BE6D4AEA7ACE /* libPods-Runner.a */, - ); - name = Frameworks; - sourceTree = ""; - }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -88,8 +72,6 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - C4CD52B830EB22224036641E /* Pods */, - 50488B4156A6CC29CC303635 /* Frameworks */, ); sourceTree = ""; }; @@ -104,36 +86,18 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 97C146F21CF9000F007C117D /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - C4CD52B830EB22224036641E /* Pods */ = { - isa = PBXGroup; - children = ( - 9235FC581E2F702C430A0573 /* Pods-Runner.debug.xcconfig */, - C9CB04F761683177D49AF7FB /* Pods-Runner.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -141,7 +105,6 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - EE5E807A2025436A87389B42 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, @@ -164,20 +127,20 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1130; - ORGANIZATIONNAME = "The Chromium Authors"; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, Base, ); @@ -197,7 +160,6 @@ buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, @@ -235,24 +197,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - EE5E807A2025436A87389B42 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - 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"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -260,8 +204,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, - 97C146F31CF9000F007C117D /* main.m in Sources */, + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -288,11 +231,81 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.demoMigaration; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -336,7 +349,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -348,7 +361,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -386,9 +398,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -399,20 +414,19 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CURRENT_PROJECT_VERSION = 1; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.app; + PRODUCT_BUNDLE_IDENTIFIER = com.example.demoMigaration; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -422,20 +436,18 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CURRENT_PROJECT_VERSION = 1; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.app; + PRODUCT_BUNDLE_IDENTIFIER = com.example.demoMigaration; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -448,6 +460,7 @@ buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -457,6 +470,7 @@ buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/packages/graphql_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/graphql_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/graphql_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/graphql_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/graphql_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/graphql_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/graphql_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/graphql_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index eff2bca03..c87d15a33 100644 --- a/packages/graphql_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/graphql_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - diff --git a/packages/graphql_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/graphql_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/graphql_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/graphql_flutter/example/ios/Runner/AppDelegate.h b/packages/graphql_flutter/example/ios/Runner/AppDelegate.h deleted file mode 100644 index cf210d213..000000000 --- a/packages/graphql_flutter/example/ios/Runner/AppDelegate.h +++ /dev/null @@ -1,6 +0,0 @@ -#import -#import - -@interface AppDelegate : FlutterAppDelegate - -@end diff --git a/packages/graphql_flutter/example/ios/Runner/AppDelegate.m b/packages/graphql_flutter/example/ios/Runner/AppDelegate.m deleted file mode 100644 index 112becd13..000000000 --- a/packages/graphql_flutter/example/ios/Runner/AppDelegate.m +++ /dev/null @@ -1,12 +0,0 @@ -#include "AppDelegate.h" -#include "GeneratedPluginRegistrant.h" - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - // Override point for customization after application launch. - return [super application:application didFinishLaunchingWithOptions:launchOptions]; -} - -@end diff --git a/packages/graphql_flutter/example/ios/Runner/AppDelegate.swift b/packages/graphql_flutter/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/packages/graphql_flutter/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/graphql_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/graphql_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index 3d43d11e6..dc9ada472 100644 Binary files a/packages/graphql_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/packages/graphql_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/graphql_flutter/example/ios/Runner/Info.plist b/packages/graphql_flutter/example/ios/Runner/Info.plist index 15c9907f8..a2a66afa5 100644 --- a/packages/graphql_flutter/example/ios/Runner/Info.plist +++ b/packages/graphql_flutter/example/ios/Runner/Info.plist @@ -3,7 +3,9 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Demo Migaration CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,15 +13,15 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - app + demo_migaration CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion - 1 + $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName @@ -40,6 +42,6 @@ UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance - + diff --git a/packages/graphql_flutter/example/ios/Runner/Runner-Bridging-Header.h b/packages/graphql_flutter/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/graphql_flutter/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/graphql_flutter/example/ios/Runner/main.m b/packages/graphql_flutter/example/ios/Runner/main.m deleted file mode 100644 index 0ccc45001..000000000 --- a/packages/graphql_flutter/example/ios/Runner/main.m +++ /dev/null @@ -1,9 +0,0 @@ -#import -#import -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/packages/graphql_flutter/example/lib/fetchmore/main.dart b/packages/graphql_flutter/example/lib/fetchmore/main.dart index dfa5f98a7..ea9863fb9 100644 --- a/packages/graphql_flutter/example/lib/fetchmore/main.dart +++ b/packages/graphql_flutter/example/lib/fetchmore/main.dart @@ -11,19 +11,15 @@ class FetchMoreWidgetScreen extends StatelessWidget { @override Widget build(BuildContext context) { - final httpLink = HttpLink('https://api.github.com/graphql'); - - final authLink = AuthLink( - // ignore: undefined_identifier - getToken: () => 'Bearer $YOUR_PERSONAL_ACCESS_TOKEN', - ); - - final link = authLink.concat(httpLink); + final httpLink = + HttpLink('https://api.github.com/graphql', defaultHeaders: { + 'Authorization': 'Bearer $YOUR_PERSONAL_ACCESS_TOKEN', + }); final client = ValueNotifier( GraphQLClient( cache: GraphQLCache(), - link: link, + link: httpLink, ), ); diff --git a/packages/graphql_flutter/example/lib/generated_plugin_registrant.dart b/packages/graphql_flutter/example/lib/generated_plugin_registrant.dart new file mode 100644 index 000000000..3c36ef502 --- /dev/null +++ b/packages/graphql_flutter/example/lib/generated_plugin_registrant.dart @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +// ignore_for_file: directives_ordering +// ignore_for_file: lines_longer_than_80_chars + +import 'package:connectivity_plus_web/connectivity_plus_web.dart'; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +// ignore: public_member_api_docs +void registerPlugins(Registrar registrar) { + ConnectivityPlusPlugin.registerWith(registrar); + registrar.registerMessageHandler(); +} diff --git a/packages/graphql_flutter/example/lib/graphql_bloc/bloc.dart b/packages/graphql_flutter/example/lib/graphql_bloc/bloc.dart index 6b0d89630..395e3bcf6 100644 --- a/packages/graphql_flutter/example/lib/graphql_bloc/bloc.dart +++ b/packages/graphql_flutter/example/lib/graphql_bloc/bloc.dart @@ -52,26 +52,17 @@ class Bloc { Sink get updateNumberOfRepoSink => _updateNumberOfRepo; - static final HttpLink _httpLink = HttpLink( - 'https://api.github.com/graphql', - ); - - static final AuthLink _authLink = AuthLink( - // ignore: undefined_identifier - getToken: () async => 'Bearer $YOUR_PERSONAL_ACCESS_TOKEN', - ); - - static final Link _link = _authLink.concat(_httpLink); - - static final GraphQLClient _client = GraphQLClient( + final GraphQLClient _client = GraphQLClient( cache: GraphQLCache(), - link: _link, + link: HttpLink('https://api.github.com/graphql', defaultHeaders: { + 'Authorization': 'Bearer $YOUR_PERSONAL_ACCESS_TOKEN', + }), ); Future _mutateToggleStar(Repo repo) async { final _options = MutationOptions( - document: - gql(repo.viewerHasStarred! ? mutations.removeStar : mutations.addStar), + document: gql( + repo.viewerHasStarred! ? mutations.removeStar : mutations.addStar), variables: { 'starrableId': repo.id, }, diff --git a/packages/graphql_flutter/example/lib/graphql_bloc/main.dart b/packages/graphql_flutter/example/lib/graphql_bloc/main.dart index 4613d44d9..c6e3eaada 100644 --- a/packages/graphql_flutter/example/lib/graphql_bloc/main.dart +++ b/packages/graphql_flutter/example/lib/graphql_bloc/main.dart @@ -45,8 +45,7 @@ class _MyHomePageState extends State { builder: (BuildContext context, AsyncSnapshot?> snapshot) { if (snapshot.hasError) { - return Text('\nErrors: \n ' + - (snapshot.error as List).join(',\n ')); + return Text('\nErrors: ${snapshot.error.toString()}'); } if (snapshot.data == null) { return const Center( diff --git a/packages/graphql_flutter/example/lib/graphql_operation/queries/readRepositories.dart b/packages/graphql_flutter/example/lib/graphql_operation/queries/readRepositories.dart index 62eb332a0..225384e58 100644 --- a/packages/graphql_flutter/example/lib/graphql_operation/queries/readRepositories.dart +++ b/packages/graphql_flutter/example/lib/graphql_operation/queries/readRepositories.dart @@ -42,6 +42,7 @@ const String searchRepositories = r''' const String testSubscription = r''' subscription test { deviceChanged(id: 2) { + __typename id name } diff --git a/packages/graphql_flutter/example/lib/graphql_widget/main.dart b/packages/graphql_flutter/example/lib/graphql_widget/main.dart index d02ca0494..70a42e154 100644 --- a/packages/graphql_flutter/example/lib/graphql_widget/main.dart +++ b/packages/graphql_flutter/example/lib/graphql_widget/main.dart @@ -8,38 +8,19 @@ import '../helpers.dart' show withGenericHandling; // to run the example, replace with your GitHub token in ../local.dart import '../local.dart'; -const bool ENABLE_WEBSOCKETS = false; - class GraphQLWidgetScreen extends StatelessWidget { const GraphQLWidgetScreen() : super(); @override Widget build(BuildContext context) { - final httpLink = HttpLink( - 'https://api.github.com/graphql', - ); - - final authLink = AuthLink( - // ignore: undefined_identifier - getToken: () async => 'Bearer $YOUR_PERSONAL_ACCESS_TOKEN', - ); - - var link = authLink.concat(httpLink); - - if (ENABLE_WEBSOCKETS) { - final websocketLink = WebSocketLink('ws://localhost:8080/ws/graphql'); - - link = Link.split( - (request) => request.isSubscription, - websocketLink, - link, - ); - } + var httpLink = HttpLink('https://api.github.com/graphql', defaultHeaders: { + 'Authorization': 'Bearer $YOUR_PERSONAL_ACCESS_TOKEN', + }); final client = ValueNotifier( GraphQLClient( cache: GraphQLCache(), - link: link, + link: httpLink, ), ); @@ -128,16 +109,6 @@ class _MyHomePageState extends State { }, ), ), - ENABLE_WEBSOCKETS - ? Subscription( - options: SubscriptionOptions( - document: gql(queries.testSubscription), - ), - builder: (result) => result.isLoading - ? const Text('Loading...') - : Text(result.data.toString()), - ) - : const Text(''), ], ), ), @@ -152,16 +123,16 @@ class StarrableRepository extends StatelessWidget { required this.optimistic, }) : super(key: key); - final Map repository; + final Map repository; final bool optimistic; /// Extract the repository data for updating the fragment - Map? extractRepositoryData(Map data) { - final action = data['action'] as Map?; + Map? extractRepositoryData(Map data) { + final action = data['action'] as Map?; if (action == null) { return null; } - return action['starrable'] as Map?; + return action['starrable'] as Map?; } /// Get whether the repository is currently starred, according to the current Query diff --git a/packages/graphql_flutter/example/lib/local.dart b/packages/graphql_flutter/example/lib/local.dart index 6d40e426a..8e5891184 100644 --- a/packages/graphql_flutter/example/lib/local.dart +++ b/packages/graphql_flutter/example/lib/local.dart @@ -1 +1 @@ -const String YOUR_PERSONAL_ACCESS_TOKEN = ''; +const String YOUR_PERSONAL_ACCESS_TOKEN = ''; diff --git a/packages/graphql_flutter/example/lib/main.dart b/packages/graphql_flutter/example/lib/main.dart index 63c7719f4..9a27ead63 100644 --- a/packages/graphql_flutter/example/lib/main.dart +++ b/packages/graphql_flutter/example/lib/main.dart @@ -1,62 +1,72 @@ import 'package:flutter/material.dart'; +import 'package:trash_themes/themes.dart'; import './graphql_bloc/main.dart' show GraphQLBlocPatternScreen; import './graphql_widget/main.dart' show GraphQLWidgetScreen; import 'fetchmore/main.dart'; -void main() => runApp( - MaterialApp( - title: 'GraphQL Flutter Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: Builder( - builder: (BuildContext context) => Scaffold( - appBar: AppBar( - title: const Text('GraphQL Demo App'), - ), - body: Center( - child: Column( - children: [ - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => - GraphQLBlocPatternScreen(), - ), - ); - }, - child: const Text('GraphQL BloC pattern'), - ), - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => - const GraphQLWidgetScreen(), - ), - ); - }, - child: const Text('GraphQL Widget'), - ), - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => - const FetchMoreWidgetScreen(), - ), - ); - }, - child: const Text('Fetchmore (Pagination) Example'), - ), - ], - ), +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'GraphQL Flutter Demo', + theme: DraculaTheme().makeDarkTheme(context: context), + home: Builder( + builder: (BuildContext context) => Scaffold( + appBar: AppBar( + title: const Text('GraphQL Demo App'), + ), + body: Center( + child: Column( + children: [ + Spacer(), + Flexible( + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => + GraphQLBlocPatternScreen(), + ), + ); + }, + child: const Text('GraphQL BloC pattern'), + )), + Spacer(), + Flexible( + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => + const GraphQLWidgetScreen(), + ), + ); + }, + child: const Text('GraphQL Widget'), + )), + Spacer(), + Flexible( + child: ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => + const FetchMoreWidgetScreen(), + ), + ); + }, + child: const Text('Fetchmore (Pagination) Example'), + )), + ], ), ), ), ), ); + } +} diff --git a/packages/graphql_flutter/example/pubspec.yaml b/packages/graphql_flutter/example/pubspec.yaml index 77c039666..e26db97df 100644 --- a/packages/graphql_flutter/example/pubspec.yaml +++ b/packages/graphql_flutter/example/pubspec.yaml @@ -4,20 +4,21 @@ description: A new Flutter project. publish_to: none module: - androidX: true // Add this line. + androidX: true dependencies: flutter: sdk: flutter - cupertino_icons: ^0.1.2 + cupertino_icons: ^0.1.3 graphql_flutter: path: .. + trash_themes: ^0.0.1 dev_dependencies: pedantic: ^1.8.0+1 flutter_test: sdk: flutter - test: ^1.5.1 + test: ^1.17.12 flutter: uses-material-design: true diff --git a/packages/graphql_flutter/example/web/favicon.png b/packages/graphql_flutter/example/web/favicon.png new file mode 100644 index 000000000..8aaa46ac1 Binary files /dev/null and b/packages/graphql_flutter/example/web/favicon.png differ diff --git a/packages/graphql_flutter/example/web/icons/Icon-192.png b/packages/graphql_flutter/example/web/icons/Icon-192.png new file mode 100644 index 000000000..b749bfef0 Binary files /dev/null and b/packages/graphql_flutter/example/web/icons/Icon-192.png differ diff --git a/packages/graphql_flutter/example/web/icons/Icon-512.png b/packages/graphql_flutter/example/web/icons/Icon-512.png new file mode 100644 index 000000000..88cfd48df Binary files /dev/null and b/packages/graphql_flutter/example/web/icons/Icon-512.png differ diff --git a/packages/graphql_flutter/example/web/icons/Icon-maskable-192.png b/packages/graphql_flutter/example/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000..eb9b4d76e Binary files /dev/null and b/packages/graphql_flutter/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/graphql_flutter/example/web/icons/Icon-maskable-512.png b/packages/graphql_flutter/example/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000..d69c56691 Binary files /dev/null and b/packages/graphql_flutter/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/graphql_flutter/example/web/index.html b/packages/graphql_flutter/example/web/index.html new file mode 100644 index 000000000..60109c1bc --- /dev/null +++ b/packages/graphql_flutter/example/web/index.html @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + demo_migaration + + + + + + + diff --git a/packages/graphql_flutter/example/web/manifest.json b/packages/graphql_flutter/example/web/manifest.json new file mode 100644 index 000000000..fa0b5a9f2 --- /dev/null +++ b/packages/graphql_flutter/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "demo_migaration", + "short_name": "demo_migaration", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/graphql_flutter/lib/graphql_flutter.dart b/packages/graphql_flutter/lib/graphql_flutter.dart index a8f37a5c9..cbdc2a017 100644 --- a/packages/graphql_flutter/lib/graphql_flutter.dart +++ b/packages/graphql_flutter/lib/graphql_flutter.dart @@ -10,4 +10,9 @@ export 'package:graphql_flutter/src/widgets/query.dart'; export 'package:graphql_flutter/src/widgets/subscription.dart'; export 'package:graphql_flutter/src/widgets/result_accumulator.dart'; +export 'package:graphql_flutter/src/widgets/hooks/mutation.dart'; +export 'package:graphql_flutter/src/widgets/hooks/query.dart'; +export 'package:graphql_flutter/src/widgets/hooks/subscription.dart'; +export 'package:graphql_flutter/src/widgets/hooks/watch_query.dart'; + export 'package:graphql_flutter/src/hive_init.dart'; diff --git a/packages/graphql_flutter/lib/src/hive_init.dart b/packages/graphql_flutter/lib/src/hive_init.dart index f47064179..d158a69f6 100644 --- a/packages/graphql_flutter/lib/src/hive_init.dart +++ b/packages/graphql_flutter/lib/src/hive_init.dart @@ -15,7 +15,9 @@ import 'package:graphql/client.dart' show HiveStore; /// Extracted from [`hive_flutter` source][github] /// /// [github]: https://github.com/hivedb/hive/blob/5bf355496650017409fef4e9905e8826c5dc5bf3/hive_flutter/lib/src/hive_extensions.dart -Future initHiveForFlutter({String? subDir, Iterable boxes = const [ HiveStore.defaultBoxName ] }) async { +Future initHiveForFlutter( + {String? subDir, + Iterable boxes = const [HiveStore.defaultBoxName]}) async { WidgetsFlutterBinding.ensureInitialized(); if (!kIsWeb) { var appDir = await getApplicationDocumentsDirectory(); @@ -26,8 +28,6 @@ Future initHiveForFlutter({String? subDir, Iterable boxes = const Hive.init(path); } - for (var box in boxes){ - await Hive.openBox(box); - } - + final futures = boxes.map(Hive.openBox); + await Future.wait(futures); } diff --git a/packages/graphql_flutter/lib/src/widgets/graphql_provider.dart b/packages/graphql_flutter/lib/src/widgets/graphql_provider.dart index d87b5d1ee..52959edaf 100644 --- a/packages/graphql_flutter/lib/src/widgets/graphql_provider.dart +++ b/packages/graphql_flutter/lib/src/widgets/graphql_provider.dart @@ -49,7 +49,7 @@ class _InheritedGraphQLProvider extends InheritedWidget { _InheritedGraphQLProvider({ required this.client, required Widget child, - }) : clientValue = client.value, + }) : clientValue = client.value, super(child: child); factory _InheritedGraphQLProvider.of(BuildContext context) { diff --git a/packages/graphql_flutter/lib/src/widgets/hooks/graphql_client.dart b/packages/graphql_flutter/lib/src/widgets/hooks/graphql_client.dart new file mode 100644 index 000000000..17b93814f --- /dev/null +++ b/packages/graphql_flutter/lib/src/widgets/hooks/graphql_client.dart @@ -0,0 +1,8 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:graphql/client.dart'; +import 'package:graphql_flutter/src/widgets/graphql_provider.dart'; + +GraphQLClient useGraphQLClient() { + final context = useContext(); + return useValueListenable(GraphQLProvider.of(context)); +} diff --git a/packages/graphql_flutter/lib/src/widgets/hooks/mutation.dart b/packages/graphql_flutter/lib/src/widgets/hooks/mutation.dart new file mode 100644 index 000000000..20ad01c45 --- /dev/null +++ b/packages/graphql_flutter/lib/src/widgets/hooks/mutation.dart @@ -0,0 +1,54 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:graphql/client.dart'; +import 'package:graphql_flutter/src/widgets/hooks/graphql_client.dart'; +import 'package:graphql_flutter/src/widgets/hooks/watch_query.dart'; + +typedef RunMutation = MultiSourceResult Function( + Map variables, { + Object? optimisticResult, +}); + +class MutationHookResult { + final RunMutation runMutation; + final QueryResult result; + + MutationHookResult({ + required this.runMutation, + required this.result, + }); +} + +MutationHookResult useMutation( + MutationOptions options, +) { + final watchOptions = useMemoized( + () => options.asWatchQueryOptions(), + [options], + ); + final client = useGraphQLClient(); + final query = useWatchMutation(watchOptions); + final snapshot = useStream( + query.stream, + initialData: query.latestResult ?? QueryResult.unexecuted, + ); + final runMutation = useCallback(( + Map variables, { + Object? optimisticResult, + }) { + final mutationCallbacks = MutationCallbackHandler( + cache: client.cache, + queryId: query.queryId, + options: options, + ); + return (query + ..variables = variables + ..optimisticResult = optimisticResult + ..onData(mutationCallbacks.callbacks) // add callbacks to observable + ) + .fetchResults(); + }, [client, query, options]); + return MutationHookResult( + runMutation: runMutation, + result: snapshot.data!, + ); +} diff --git a/packages/graphql_flutter/lib/src/widgets/hooks/query.dart b/packages/graphql_flutter/lib/src/widgets/hooks/query.dart new file mode 100644 index 000000000..19a768746 --- /dev/null +++ b/packages/graphql_flutter/lib/src/widgets/hooks/query.dart @@ -0,0 +1,38 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:graphql/client.dart'; +import 'package:graphql_flutter/src/widgets/hooks/watch_query.dart'; + +// method to call from widget to fetchmore queries +typedef FetchMore = Future> Function( + FetchMoreOptions options); + +typedef Refetch = Future?> Function(); + +class QueryHookResult { + final QueryResult result; + final Refetch refetch; + final FetchMore fetchMore; + + QueryHookResult({ + required this.result, + required this.refetch, + required this.fetchMore, + }); +} + +QueryHookResult useQuery(QueryOptions options) { + final watchQueryOptions = useMemoized( + () => options.asWatchQueryOptions(), + [options], + ); + final query = useWatchQuery(watchQueryOptions); + final snapshot = useStream( + query.stream, + initialData: query.latestResult, + ); + return QueryHookResult( + result: snapshot.data!, + refetch: query.refetch, + fetchMore: query.fetchMore, + ); +} diff --git a/packages/graphql_flutter/lib/src/widgets/hooks/subscription.dart b/packages/graphql_flutter/lib/src/widgets/hooks/subscription.dart new file mode 100644 index 000000000..4ca55b7aa --- /dev/null +++ b/packages/graphql_flutter/lib/src/widgets/hooks/subscription.dart @@ -0,0 +1,129 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:graphql/client.dart'; +import 'package:graphql_flutter/src/widgets/hooks/graphql_client.dart'; + +typedef OnSubscriptionResult = void Function( + QueryResult subscriptionResult, + GraphQLClient? client, +); + +typedef SubscriptionBuilder = Widget Function( + QueryResult result); + +QueryResult useSubscription( + SubscriptionOptions options, { + OnSubscriptionResult? onSubscriptionResult, +}) { + final client = useGraphQLClient(); + final stream = use(_SubscriptionHook( + client: client, + onSubscriptionResult: onSubscriptionResult, + options: options, + )); + final snapshot = useStream( + stream, + initialData: options.optimisticResult != null + ? QueryResult.optimistic( + data: options.optimisticResult as Map?, + parserFn: options.parserFn, + ) + : QueryResult.loading(parserFn: options.parserFn), + ); + return snapshot.data!; +} + +class _SubscriptionHook extends Hook>> { + final SubscriptionOptions options; + final GraphQLClient client; + final OnSubscriptionResult? onSubscriptionResult; + _SubscriptionHook({ + required this.options, + required this.client, + required this.onSubscriptionResult, + }); + @override + HookState>, Hook>>> + createState() { + return _SubscriptionHookState(); + } +} + +class _SubscriptionHookState extends HookState< + Stream>, _SubscriptionHook> { + late Stream> stream; + GraphQLClient? client; + + ConnectivityResult? _currentConnectivityResult; + StreamSubscription? _networkSubscription; + + void _initSubscription() { + stream = client!.subscribe(hook.options); + final onSubscriptionResult = hook.onSubscriptionResult; + if (onSubscriptionResult != null) { + stream = stream.map((result) { + onSubscriptionResult(result, client); + return result; + }); + } + } + + @override + void initHook() { + super.initHook(); + _networkSubscription = + Connectivity().onConnectivityChanged.listen(_onNetworkChange); + } + + @override + void didUpdateHook(_SubscriptionHook oldHook) { + super.didUpdateHook(oldHook); + + if (hook.options != oldHook.options || hook.client != oldHook.client) { + _initSubscription(); + } + } + + @override + void dispose() { + _networkSubscription?.cancel(); + super.dispose(); + } + + Future _onNetworkChange(ConnectivityResult result) async { + //if from offline to online + if (_currentConnectivityResult == ConnectivityResult.none && + (result == ConnectivityResult.mobile || + result == ConnectivityResult.wifi)) { + _currentConnectivityResult = result; + + // android connectivitystate cannot be trusted + // validate with nslookup + if (Platform.isAndroid) { + try { + final nsLookupResult = await InternetAddress.lookup('google.com'); + if (nsLookupResult.isNotEmpty && + nsLookupResult[0].rawAddress.isNotEmpty) { + _initSubscription(); + } + // on exception -> no real connection, set current state to none + } on SocketException catch (_) { + _currentConnectivityResult = ConnectivityResult.none; + } + } else { + _initSubscription(); + } + } else { + _currentConnectivityResult = result; + } + } + + @override + Stream> build(BuildContext context) { + return stream; + } +} diff --git a/packages/graphql_flutter/lib/src/widgets/hooks/watch_query.dart b/packages/graphql_flutter/lib/src/widgets/hooks/watch_query.dart new file mode 100644 index 000000000..e37f6904f --- /dev/null +++ b/packages/graphql_flutter/lib/src/widgets/hooks/watch_query.dart @@ -0,0 +1,95 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:graphql/client.dart'; +import 'package:graphql_flutter/src/widgets/hooks/graphql_client.dart'; + +class _WatchQueryHook extends Hook> { + final GraphQLClient client; + final WatchQueryOptions options; + + _WatchQueryHook({ + required this.options, + required this.client, + }); + + @override + HookState, Hook>> + createState() { + return _WatchQueryHookState(); + } +} + +class _WatchQueryHookState + extends HookState, _WatchQueryHook> { + late ObservableQuery _observableQuery; + + @override + initHook() { + super.initHook(); + _connect(); + } + + @override + dispose() { + _close(); + super.dispose(); + } + + _connect() { + _observableQuery = hook.client.queryManager.watchQuery(hook.options); + } + + _close() { + _observableQuery.close(); + } + + _reconnect() { + _close(); + _connect(); + } + + @override + didUpdateHook(oldHook) { + super.didUpdateHook(oldHook); + if (oldHook.client == hook.client && oldHook.options == hook.options) { + return; + } + _reconnect(); + } + + ObservableQuery build(BuildContext context) { + return _observableQuery; + } +} + +ObservableQuery useWatchQuery( + WatchQueryOptions options, +) { + final client = useGraphQLClient(); + final overwrittenOptions = useMemoized(() { + final policies = + client.defaultPolicies.query.withOverrides(options.policies); + return options.copyWithPolicies(policies); + }, [options]); + + return use(_WatchQueryHook( + options: overwrittenOptions, + client: client, + )); +} + +ObservableQuery useWatchMutation( + WatchQueryOptions options) { + final client = useGraphQLClient(); + final overwrittenOptions = useMemoized(() { + final policies = + client.defaultPolicies.mutate.withOverrides(options.policies); + return options.copyWithPolicies(policies); + }, [options]); + return use( + _WatchQueryHook( + options: overwrittenOptions, + client: client, + ), + ); +} diff --git a/packages/graphql_flutter/lib/src/widgets/mutation.dart b/packages/graphql_flutter/lib/src/widgets/mutation.dart index 427233bfa..841e8b5f1 100644 --- a/packages/graphql_flutter/lib/src/widgets/mutation.dart +++ b/packages/graphql_flutter/lib/src/widgets/mutation.dart @@ -1,133 +1,28 @@ import 'package:flutter/widgets.dart'; - +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:graphql/client.dart'; +import 'package:graphql_flutter/src/widgets/hooks/mutation.dart'; -import 'package:graphql_flutter/src/widgets/graphql_provider.dart'; - -typedef RunMutation = MultiSourceResult Function( - Map variables, { - Object? optimisticResult, -}); - -typedef MutationBuilder = Widget Function( - RunMutation runMutation, - QueryResult? result, +typedef MutationBuilder = Widget Function( + RunMutation runMutation, + QueryResult? result, ); /// Builds a [Mutation] widget based on the a given set of [MutationOptions] /// that streams [QueryResult]s into the [QueryBuilder]. -class Mutation extends StatefulWidget { +class Mutation extends HookWidget { const Mutation({ final Key? key, required this.options, required this.builder, }) : super(key: key); - final MutationOptions options; - final MutationBuilder builder; - - @override - MutationState createState() => MutationState(); -} - -class MutationState extends State { - GraphQLClient? client; - ObservableQuery? observableQuery; - - WatchQueryOptions? __cachedOptions; - - WatchQueryOptions get _providedOptions { - final _options = WatchQueryOptions( - document: widget.options.document, - operationName: widget.options.operationName, - variables: widget.options.variables, - fetchPolicy: widget.options.fetchPolicy, - errorPolicy: widget.options.errorPolicy, - cacheRereadPolicy: widget.options.cacheRereadPolicy, - fetchResults: false, - context: widget.options.context, - ); - __cachedOptions ??= _options; - return _options; - } - - /// sets new options, returning true if they didn't equal the old - bool _setNewOptions() { - final _cached = __cachedOptions; - final _new = _providedOptions; - if (_cached == null || !_new.equal(_cached)) { - __cachedOptions = _new; - return true; - } - return false; - } - - // TODO is it possible to extract shared logic into mixin - void _initQuery() { - observableQuery?.close(); - observableQuery = client!.watchMutation(_providedOptions.copy()); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final GraphQLClient client = GraphQLProvider.of(context).value; - if (client != this.client) { - this.client = client; - _initQuery(); - } - } - - @override - void didUpdateWidget(Mutation oldWidget) { - super.didUpdateWidget(oldWidget); - - // TODO @micimize - investigate why/if this was causing issues - if (_setNewOptions()) { - _initQuery(); - } - } - - /// Run the mutation with the given `variables` and `optimisticResult`, - /// returning a [MultiSourceResult] for handling both the eager and network results - MultiSourceResult runMutation( - Map variables, { - Object? optimisticResult, - }) { - final mutationCallbacks = MutationCallbackHandler( - cache: client!.cache, - queryId: observableQuery!.queryId, - options: widget.options, - ); - - return (observableQuery! - ..variables = variables - ..options.optimisticResult = optimisticResult - ..onData(mutationCallbacks.callbacks) // add callbacks to observable - ) - .fetchResults(); - } - - @override - void dispose() { - observableQuery?.close(force: false); - super.dispose(); - } + final MutationOptions options; + final MutationBuilder builder; @override Widget build(BuildContext context) { - return StreamBuilder( - initialData: observableQuery?.latestResult ?? QueryResult.unexecuted, - stream: observableQuery?.stream, - builder: ( - BuildContext buildContext, - AsyncSnapshot snapshot, - ) { - return widget.builder( - runMutation, - snapshot.data, - ); - }, - ); + final result = useMutation(options); + return builder(result.runMutation, result.result); } } diff --git a/packages/graphql_flutter/lib/src/widgets/query.dart b/packages/graphql_flutter/lib/src/widgets/query.dart index 25636f469..f8bf425db 100644 --- a/packages/graphql_flutter/lib/src/widgets/query.dart +++ b/packages/graphql_flutter/lib/src/widgets/query.dart @@ -1,93 +1,33 @@ import 'package:flutter/widgets.dart'; - +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:graphql/client.dart'; +import 'package:graphql_flutter/src/widgets/hooks/query.dart'; -import 'package:graphql_flutter/src/widgets/graphql_provider.dart'; - -// method to call from widget to fetchmore queries -typedef FetchMore = Future Function(FetchMoreOptions options); - -typedef Refetch = Future Function(); - -typedef QueryBuilder = Widget Function( - QueryResult result, { - Refetch? refetch, - FetchMore? fetchMore, +typedef QueryBuilder = Widget Function( + QueryResult result, { + Refetch? refetch, + FetchMore? fetchMore, }); /// Builds a [Query] widget based on the a given set of [QueryOptions] /// that streams [QueryResult]s into the [QueryBuilder]. -class Query extends StatefulWidget { +class Query extends HookWidget { const Query({ final Key? key, required this.options, required this.builder, }) : super(key: key); - final QueryOptions options; - final QueryBuilder builder; - - @override - QueryState createState() => QueryState(); -} - -class QueryState extends State { - ObservableQuery? observableQuery; - GraphQLClient? _client; - - WatchQueryOptions get _options => widget.options.asWatchQueryOptions(); - - void _initQuery() { - observableQuery?.close(); - observableQuery = _client!.watchQuery(_options); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final GraphQLClient client = GraphQLProvider.of(context).value; - if (client != _client) { - _client = client; - _initQuery(); - } - } - - @override - void didUpdateWidget(Query oldWidget) { - super.didUpdateWidget(oldWidget); - - final GraphQLClient client = GraphQLProvider.of(context).value; - - final optionsWithOverrides = _options; - optionsWithOverrides.policies = client.defaultPolicies.watchQuery - .withOverrides(optionsWithOverrides.policies); - - if (!observableQuery!.options.equal(optionsWithOverrides)) { - _initQuery(); - } - } - - @override - void dispose() { - observableQuery?.close(); - super.dispose(); - } + final QueryOptions options; + final QueryBuilder builder; @override Widget build(BuildContext context) { - return StreamBuilder( - initialData: observableQuery?.latestResult ?? QueryResult.loading(), - stream: observableQuery!.stream, - builder: ( - BuildContext buildContext, - AsyncSnapshot snapshot, - ) { - return widget.builder( - snapshot.data!, - refetch: observableQuery!.refetch, - fetchMore: observableQuery!.fetchMore, - ); - }, + final result = useQuery(options); + return builder( + result.result, + fetchMore: result.fetchMore, + refetch: result.refetch, ); } } diff --git a/packages/graphql_flutter/lib/src/widgets/subscription.dart b/packages/graphql_flutter/lib/src/widgets/subscription.dart index d89dd6cbe..28b245c2c 100644 --- a/packages/graphql_flutter/lib/src/widgets/subscription.dart +++ b/packages/graphql_flutter/lib/src/widgets/subscription.dart @@ -1,18 +1,7 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:graphql/client.dart'; -import 'package:graphql_flutter/graphql_flutter.dart'; -import 'package:graphql_flutter/src/widgets/graphql_provider.dart'; - -typedef OnSubscriptionResult = void Function( - QueryResult subscriptionResult, - GraphQLClient? client, -); - -typedef SubscriptionBuilder = Widget Function(QueryResult result); +import 'package:graphql_flutter/src/widgets/hooks/subscription.dart'; /// Creats a subscription with [GraphQLClient.subscribe]. /// @@ -64,7 +53,7 @@ typedef SubscriptionBuilder = Widget Function(QueryResult result); /// } /// ``` /// {@end-tool} -class Subscription extends StatefulWidget { +class Subscription extends HookWidget { const Subscription({ required this.options, required this.builder, @@ -72,108 +61,16 @@ class Subscription extends StatefulWidget { Key? key, }) : super(key: key); - final SubscriptionOptions options; - final SubscriptionBuilder builder; - final OnSubscriptionResult? onSubscriptionResult; - - @override - _SubscriptionState createState() => _SubscriptionState(); -} - -class _SubscriptionState extends State { - Stream? stream; - GraphQLClient? client; - - ConnectivityResult? _currentConnectivityResult; - StreamSubscription? _networkSubscription; - - void _initSubscription() { - stream = client!.subscribe(widget.options); - - if (widget.onSubscriptionResult != null) { - stream = stream!.map((result) { - widget.onSubscriptionResult!(result, client); - return result; - }); - } - } - - @override - void initState() { - _networkSubscription = - Connectivity().onConnectivityChanged.listen(_onNetworkChange); - - super.initState(); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final GraphQLClient newClient = GraphQLProvider.of(context).value; - if (client != newClient) { - client = newClient; - _initSubscription(); - } - } - - @override - void didUpdateWidget(Subscription oldWidget) { - super.didUpdateWidget(oldWidget); - - if (!widget.options.equal(oldWidget.options)) { - _initSubscription(); - } - } - - @override - void dispose() { - _networkSubscription?.cancel(); - super.dispose(); - } - - Future _onNetworkChange(ConnectivityResult result) async { - //if from offline to online - if (_currentConnectivityResult == ConnectivityResult.none && - (result == ConnectivityResult.mobile || - result == ConnectivityResult.wifi)) { - _currentConnectivityResult = result; - - // android connectivitystate cannot be trusted - // validate with nslookup - if (Platform.isAndroid) { - try { - final nsLookupResult = await InternetAddress.lookup('google.com'); - if (nsLookupResult.isNotEmpty && - nsLookupResult[0].rawAddress.isNotEmpty) { - _initSubscription(); - } - // on exception -> no real connection, set current state to none - } on SocketException catch (_) { - _currentConnectivityResult = ConnectivityResult.none; - } - } else { - _initSubscription(); - } - } else { - _currentConnectivityResult = result; - } - } + final SubscriptionOptions options; + final SubscriptionBuilder builder; + final OnSubscriptionResult? onSubscriptionResult; @override Widget build(BuildContext context) { - return StreamBuilder( - initialData: widget.options.optimisticResult != null - ? QueryResult.optimistic( - data: widget.options.optimisticResult as Map?, - ) - : QueryResult.loading(), - stream: stream, - builder: ( - BuildContext buildContext, - AsyncSnapshot snapshot, - ) { - return widget.builder(snapshot.data!); - }, + final result = useSubscription( + options, + onSubscriptionResult: onSubscriptionResult, ); + return builder(result); } } diff --git a/packages/graphql_flutter/pubspec.yaml b/packages/graphql_flutter/pubspec.yaml index 69d9998f5..c89ef797a 100644 --- a/packages/graphql_flutter/pubspec.yaml +++ b/packages/graphql_flutter/pubspec.yaml @@ -1,20 +1,19 @@ name: graphql_flutter description: A GraphQL client for Flutter, bringing all the features from a modern GraphQL client to one easy to use package. -version: 5.0.0 +version: 5.0.2-beta.3 homepage: https://github.com/zino-app/graphql-flutter/tree/master/packages/graphql_flutter -publish_to: none dependencies: - graphql: # 5.0.0 - path: ../graphql - gql_exec: ^0.3.0 + graphql: 5.0.2-beta.1 + gql_exec: 0.3.0 flutter: sdk: flutter meta: ^1.3.0 path_provider: ^2.0.1 path: ^1.8.0 - connectivity_plus: ^1.0.1 + connectivity_plus: ^2.0.3 hive: ^2.0.0 plugin_platform_interface: ^2.0.0 + flutter_hooks: ^0.18.2 dev_dependencies: pedantic: ^1.11.0 mockito: ^5.0.0 diff --git a/packages/graphql_flutter/test/widgets/query_test.dart b/packages/graphql_flutter/test/widgets/query_test.dart index 6f1be87e0..5607c3691 100644 --- a/packages/graphql_flutter/test/widgets/query_test.dart +++ b/packages/graphql_flutter/test/widgets/query_test.dart @@ -5,7 +5,6 @@ import 'package:flutter/services.dart' show MethodChannel, MethodCall; import 'package:flutter_test/flutter_test.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; -import 'package:graphql_flutter/src/widgets/query.dart'; import 'package:http/http.dart' as http; import 'package:mockito/mockito.dart'; @@ -31,19 +30,21 @@ final query = gql(""" /// https://flutter.dev/docs/cookbook/persistence/reading-writing-files#testing Future mockApplicationDocumentsDirectory() async { -// Create a temporary directory. + // Create a temporary directory. final directory = await Directory.systemTemp.createTemp(); - - // Mock out the MethodChannel for the path_provider plugin. - const MethodChannel('plugins.flutter.io/path_provider') - .setMockMethodCallHandler((MethodCall methodCall) async { + final handler = (MethodCall methodCall) async { // If you're getting the apps documents directory, return the path to the // temp directory on the test environment instead. if (methodCall.method == 'getApplicationDocumentsDirectory') { return directory.path; } return null; - }); + }; + // Mock out the MethodChannel for the path_provider plugin. + const MethodChannel('plugins.flutter.io/path_provider') + .setMockMethodCallHandler(handler); + const MethodChannel('plugins.flutter.io/path_provider_macos') + .setMockMethodCallHandler(handler); } class Page extends StatefulWidget { @@ -340,7 +341,7 @@ void main() { 'does not issues new network request when policies are effectively unchanged', (WidgetTester tester) async { final page = Page( - fetchPolicy: FetchPolicy.cacheAndNetwork, + fetchPolicy: client!.value.defaultPolicies.query.fetch, errorPolicy: null, );