Flutter : 画像の選択

Flutter + Firebase でアプリ作成のためのパーツあつめ。

画像選択のパッケージ、image_picker を試す。

https://pub.dev/packages/image_picker

のだが、AndroidX互換性問題に遭遇し難儀したが、何とか解決

1.Flutter

1.1 pubspec.yaml (/) にimage_pickerを追記

dependencies:
    :
  image_picker: ^0.6.1+8

1.2 second.dart

画面遷移で起動した画面に、画像選択し、画像表示するウィジェットを追加する (関係ないコードは除外)

ウィジェットのルートにScaffoldを使用したままだと、写真を選択したときにサイズがはみ出ると、下図のように、はみで多分に黒と黄色の車線がはいって表示され、以下のようなエラーログが吐かれる。

════════ Exception caught by rendering library ═════════════════════════════════════════════════════
The following assertion was thrown during layout:
A RenderFlex overflowed by 236 pixels on the bottom.

flutter_pick_image_err

スクロールできるようにする必要があるようなので、以下のサンプルソースに合わせて、スクロールビューの中に入れる。

https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';


class SecondRoute extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _SecondRoute();
  }
}
class _SecondRoute extends State<SecondRoute>{
  Image _image = Image.network('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg');

  Future _handleImagePick(BuildContext context) async {
    var imageFile = await ImagePicker.pickImage(source: ImageSource.gallery);
    var image = Image.file(imageFile);
    setState(() {
      _image = image;
    });
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints viewportConstraints) {
        return SingleChildScrollView(
          child: ConstrainedBox(
            constraints: BoxConstraints(
              minHeight: viewportConstraints.maxHeight,
            ),
            child: IntrinsicHeight(
              child: Column(
                children: <Widget>[
                  Container(
                    // A fixed-height child.
                    color: const Color.fromARGB(255, 255, 255, 255),
                    height: 120.0,
                  ),
                  Expanded(
                    // A flexible child that will grow to fit the viewport but
                    // still be at least as big as necessary to fit its contents.
                    child: Container(
                      color: const Color(0xffffffff),
                      height: 240.0,
                      constraints: BoxConstraints(
                        minWidth: viewportConstraints.maxWidth,
                        minHeight: double.infinity
                      ),
                      child: Column(
                        children: <Widget>[
                          RaisedButton(
                            onPressed: () {
                              _handleImagePick(context);
                            },
                            child: Text('Image Pick'),
                          ),
                          _image,
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      },
    );
  }

}

2.Android

画像の初期表示には、Flutter Imageクラスのサンプル画像を表示。

flutter_pick_image01

「Image Pick」ボタン押下でギャラリーから画像を選択できる。

var imageFile = await ImagePicker.pickImage(source: ImageSource.gallery);

のImageSource を、ImageSource.camera に変更することで、カメラが起動する!

flutter_pick_image02

選択すると、Image コンポーネントに選択された画像がロードされる!

flutter_pick_image03

3.iOS

Androidに対応して、少し変更する必要がある。

info.plist(/ios/Runner) に以下を追記。それぞれの機能(key)を利用するときに、目的(string)を表示しユーザーに許可を求める。

<dict>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>What purpose to use</string>
    <key>NSCameraUsageDescription</key>
    <string>What purpose to use</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>What purpose to use</string>
    <key>CFBundleDevelopmentRegion</key>
    :
</dict>

エミュレーターで実行。Androidと同様に表示された。

flutter_pick_image_ios01

「Image Pick」ボタンで、ユーザーに確認。OK押下。

flutter_pick_image_ios02

ギャラリー?が表示されるので、画像を選択。

flutter_pick_image_ios03

選択した画像が、表示された!

flutter_pick_image_ios04

AndroidX対応で、結構はまったが、行けそうか!?

iOSについても勉強する必要あるな。

1.パッケージエラーでまくり

XamarinからFlutterに乗り換えをもくろんで環境構築画面遷移や、Webアクセスなど必要な機能を少しずつ試していたのだが、画像選択のために image_picker パッケージを利用しようとしたところ以下のようなエラーでまくりで、ちょっと萎える。

Running "flutter pub get" in favo_phrase...                         0.9s
Launching lib\main.dart on MAR LX2J in debug mode...
Initializing gradle...
Resolving dependencies...
Running Gradle task 'assembleDebug'...
registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)
registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)
registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:processDebugResources'.
> Android resource linking failed
  Output:  C:\workspaces\FavoPhrase-workspace\trial\favo_phrase\build\app\intermediates\incremental\mergeDebugResources\merged.dir\values\values.xml:37: error: resource android:attr/fontVariationSettings not found.
  C:\workspaces\FavoPhrase-workspace\trial\favo_phrase\build\app\intermediates\incremental\mergeDebugResources\merged.dir\values\values.xml:37: error: resource android:attr/ttcIndex not found.
  error: failed linking references.

  Command: C:\Users\Hiroto Yagi\.gradle\caches\transforms-1\files-1.1\aapt2-3.2.1-4818971-windows.jar\7e99101b246de0c19b256be23ae2fa31\aapt2-3.2.1-4818971-windows\aapt2.exe link -I\
          C:\Programs\Android\Sdk\platforms\android-27\android.jar\
          --manifest\
          C:\workspaces\FavoPhrase-workspace\trial\favo_phrase\build\app\intermediates\merged_manifests\debug\processDebugManifest\merged\AndroidManifest.xml\
          -o\
          C:\workspaces\FavoPhrase-workspace\trial\favo_phrase\build\app\intermediates\processed_res\debug\processDebugResources\out\resources-debug.ap_\
          -R\
          @C:\workspaces\FavoPhrase-workspace\trial\favo_phrase\build\app\intermediates\incremental\processDebugResources\resources-list-for-resources-debug.ap_.txt\
          --auto-add-overlay\
          --java\
          C:\workspaces\FavoPhrase-workspace\trial\favo_phrase\build\app\generated\not_namespaced_r_class_sources\debug\processDebugResources\r\
          --custom-package\
          info.typea.favo_phrase\
          -0\
          apk\
          --output-text-symbols\
          C:\workspaces\FavoPhrase-workspace\trial\favo_phrase\build\app\intermediates\symbols\debug\R.txt\
          --no-version-vectors
  Daemon:  AAPT2 aapt2-3.2.1-4818971-windows Daemon #0
  Output:  C:\Users\Hiroto Yagi\.gradle\caches\transforms-1\files-1.1\core-1.0.2.aar\f850178738aab58396b10a1f10703ed8\res\values\values.xml:89:5-125:25: AAPT: error: resource android:attr/fontVariationSettings not found.

  C:\Users\Hiroto Yagi\.gradle\caches\transforms-1\files-1.1\core-1.0.2.aar\f850178738aab58396b10a1f10703ed8\res\values\values.xml:89:5-125:25: AAPT: error: resource android:attr/ttcIndex not found.

  error: failed linking references.
  Command: C:\Users\Hiroto Yagi\.gradle\caches\transforms-1\files-1.1\aapt2-3.2.1-4818971-windows.jar\7e99101b246de0c19b256be23ae2fa31\aapt2-3.2.1-4818971-windows\aapt2.exe link -I\
          C:\Programs\Android\Sdk\platforms\android-27\android.jar\
          --manifest\
          C:\workspaces\FavoPhrase-workspace\trial\favo_phrase\build\app\intermediates\merged_manifests\debug\processDebugManifest\merged\AndroidManifest.xml\
          -o\
          C:\workspaces\FavoPhrase-workspace\trial\favo_phrase\build\app\intermediates\processed_res\debug\processDebugResources\out\resources-debug.ap_\
          -R\
          @C:\workspaces\FavoPhrase-workspace\trial\favo_phrase\build\app\intermediates\incremental\processDebugResources\resources-list-for-resources-debug.ap_.txt\
          --auto-add-overlay\
          --java\
          C:\workspaces\FavoPhrase-workspace\trial\favo_phrase\build\app\generated\not_namespaced_r_class_sources\debug\processDebugResources\r\
          --custom-package\
          info.typea.favo_phrase\
          -0\
          apk\
          --output-text-symbols\
          C:\workspaces\FavoPhrase-workspace\trial\favo_phrase\build\app\intermediates\symbols\debug\R.txt\
          --no-version-vectors
  Daemon:  AAPT2 aapt2-3.2.1-4818971-windows Daemon #0

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 19s
*******************************************************************************************
The Gradle failure may have been because of AndroidX incompatibilities in this Flutter app.
See https://goo.gl/CP92wY for more information on the problem and how to fix it.
*******************************************************************************************
Finished with error: Gradle task assembleDebug failed with exit code 1

どうやら原因はAndroidXの互換性問題のようなので、適当に対処してみるが解決せず。AndroidXの理屈がわからないと解決しなさそう。しばらくAndroid触ってなかったのでAndroidX?状態なので調べる。

2.AndroidX

https://developer.android.com/jetpack/androidx?hl=ja

2.1 概要

  • Jetpack内で使用されているオープンソースプロジェクト。
    • Androidアプリを開発のためのライブラリ、ツール、ガイダンス
    • androidx.* パッケージライブラリで構成
  • 元の、Android Support Libraryを大幅に改良。
  • Android OSとは別個にリリースされ、下位互換性を提供
    • androidx で始まる名前空間に属する
    • Andrpod Support Library は androidxへ移行 対応一覧

2.2 使用する

  • AndroidXを使用する場合は、Compile SDKを Android9.0(API レベル28)以上に設定
  • Android Support Libraryを移行、パッケージ名とMavenのアーティファクト名が変更
    • クラス、メソッド、フィールドは変更せず
  • 既存のプロジェクトの移行はAndroid Studioを利用するのが吉
    • メニュー - Refactor - Migrate to AndroidX
  • AndroidXに移行されていないMaven依存関係がある場合、gradle.properties ファイルに以下を設定することで、Android Studioのビルドシステムが自動的に移行する
    • android.useAndroidX=true
      • Support Libraryではなく、該当するAndroidXを使用する
    • android.enableJetifier=true
      • ライブラリのバイナリをAndroidXを使用するように書き換える

3.対処(1)

だいたい理屈はわかったので、以下の対処

https://flutter.dev/docs/development/androidx-migration

3.1 File - Project Structure から、API レベルを28にする。SDKがダウンロードされてなければする。

flutter_androidx00

3.2 build.gradle(/android/app)) を編集

compileSdkVersion および、targetSdkVersion を28に変更

android {
    compileSdkVersion 28
     :
    defaultConfig {
          :
        targetSdkVersion 28
          :
    }
          :
}

3.3 gradle.properties(/android)に以下を追記

android.useAndroidX=true
android.enableJetifier=true

4.実行

4.1 pubspec.yaml(/) のdependencies に image_pickerimage_cropper を追記し実行

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  firebase_core: ^0.4.0+9
  firebase_auth: ^0.14.0+5
  google_sign_in: ^4.0.7
  image_picker: ^0.6.1+8
  image_cropper: ^1.1.0

4.2 以下のエラー

Android dependency 'androidx.fragment:fragment' has different version for the compile (1.0.0-rc01) and runtime (1.1.0) classpath. You should manually set the same version via DependencyResolution

うーんまだエラー出るか。。。

なかなか難儀。コンパイルとランタイムでバージョンが違うから、DependencyResolution で手動でバージョン設定しなさいと。

5.対処(2)

5.1 build.gradle(/android/app)に追記

https://github.com/flutter/flutter/issues/27254

https://qiita.com/sekitaka_1214/items/2bfa89286ebe2c08a6d8

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        print(details.requested.group)
        if (details.requested.group ==~ /^androidx\.(core|viewpager|drawlayout|interpolator|fragment|versionedparcelable)/
        ) {
            details.useVersion '1.0.0'
            details.because 'API needs not pre-release versions'
        }
        if (details.requested.group == 'androidx.lifecycle'
            ) {
            details.useVersion '2.1.0'
            details.because 'API needs not pre-release versions'
        }
    }
}

成功!

これで、image_picker パッケージを使った簡単なサンプルをAndroidおよびiPhoneエミュレーター上で動かすことが出来ました。

まぁよかったけど、環境設定で、時間もかかりすぎるし、こういうのなんとかならないかな。。。

やりたいことが出来るころには疲れ果てるわ。

書籍買うと全部うまいこと書いてくれてるのかな~?

Flutter : HttpClient

Xamarin –> Flutter の準備ができたので、

http://typea.info/blg/glob/2019/10/xamarin-flutter.html

なれるために、基本的な機能を試してみる。

に続いて、HttpClient

HttpClient

ボタンを押したら、GAEに置いた疎通用のAPIを呼び出して、結果をダイアログに表示してみる。

合わせて、HTTPヘッダの内容をログに吐き出す。

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';

class SecondRoute extends StatelessWidget{

  Future<void> _handleHello(BuildContext context) async {
    String message;
    HttpClient client = new HttpClient();
    await client.getUrl(Uri.parse("https://favophrase.com/api/hello"))
      .then((HttpClientRequest request) {
        return request.close();
      })
      .then((HttpClientResponse response) {
        print("<status> ${response.statusCode}");
        response.headers.forEach((k, v){
          print("<header> $k:$v");
        });
        response.transform(utf8.decoder).listen((contents){
          print("<contents> $contents");
          message = contents;
        });
       });

    return showDialog<void>(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text('Hello'),
          content: Column(
            children: <Widget>[
              Text(message),
            ],
          ),
          actions: <Widget>[
            FlatButton(
              child: Text('OK'),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          children: <Widget>[
            Text(
              'Second Screen.',
            ),
            RaisedButton(
              onPressed: (){
                _handleHello(context);
              },
              child: Text('Hello'),
            ),
            RaisedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: Text('Back'),
            ),
          ]
        ),
      ),
    );
  }
}

ログに、ステータスコード、HTTPヘッダ、コンテンツを出力



10-03 22:57:23.773 7372-7459/info.typea.favo_phrase I/flutter: <status> 200 10-03 22:57:23.773 7372-7459/info.typea.favo_phrase I/flutter: <header> x-cloud-trace-context:[12a5548da17d4857f598d572e6a7437d;o=1] 10-03 22:57:23.773 7372-7459/info.typea.favo_phrase I/flutter: <header> content-type:[text/plain;charset=utf-8] 10-03 22:57:23.773 7372-7459/info.typea.favo_phrase I/flutter: <header> date:[Thu, 03 Oct 2019 13:57:21 GMT] 10-03 22:57:23.773 7372-7459/info.typea.favo_phrase I/flutter: <header> content-length:[42] 10-03 22:57:23.773 7372-7459/info.typea.favo_phrase I/flutter: <header> server:[Google Frontend] 10-03 22:57:23.793 7372-7459/info.typea.favo_phrase I/flutter: <contents> Hello. This is FavoPhrase Web Service API.

flutter_httpclient1

アラートダイアログにコンテンツを表示

flutter_httpclient2

なかなかいい感じだな。