この記事は、【 可茂IT塾 Advent Calendar 2021 】の18日目の記事です。
image_pickerをriverpodで実装した記事がなかなか見つけられなかったので、アウトプット&備忘録として記事にしました。
この記事はriverpodについて詳しく説明するものではなく、備忘録としてriverpodの使い方をimgae_picker
を通してサクッと大まかに説明した記事です。
今回のプロジェクトのファイル構造は以下の画像のようになっております。厳密にこの構造にしないといけないと言うわけではなく、ご自身の好みのファイル構造で大丈夫です。
controller ←状態(データ)を管理
ui ←user interfaceの部分を記述
main ←dartのプログラムを実行した際に、最初に呼び出される関数
今回のプロジェクトでは、immutable(後から変更できない)にするためにfreezed
のパッケージを使います。ここではfreezed
についてはあまり触れないので、freezed
について知りたい方は他のサイト等を参考にして頂ければと思います。
riverpod
やfreezed
、image_picker
は外部のパッケージであるため、それを今回のプロジェクトで使うためにはpubspec.yaml
ファイルにそれらのパッケージを使えるように記述する必要があります。
image_picker
のパッケージはこちらになります。
pubspec.yaml
ファイルのdependencies
に以下のように記述します。
pubspec.yaml
ファイルは非常にデリケートなので記述する箇所のindent等には注意が必要です。
dependencies:
flutter:
sdk: flutter
freezed_annotation: ^1.0.0
state_notifier: ^0.7.1
hooks_riverpod: ^1.0.2
image_picker: ^0.8.4+4
続いてpubspec.yaml
ファイルのdev_dependencies
に以下のように記述します。
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0
build_runner: ^2.1.5
freezed: ^1.0.2+1
dependencies
やdev_dependencies
の箇所に上記のように記述したら、画面上のpub get
ボタン、もしくはターミナルでflutter pub get
を入力して実行します。
メッセージやターミナルでエラーが吐かれなければ、アプリにriverpod
やfreezed
、state_notifier
、
image_picker
のインストールが完了したことになります。
※state_notifier
はriverpod
と組み合わせて使われ、Widget
から状態(データ)とロジックを簡単に分離して、通知することができるライブラリーです。
ライブラリーを導入することができたので次は、UIを作成していきます。今回のimage_picker
のUIは以下の画像のようになっています。今回はCircleAvatar
をタップするとimage_picker
が使えるUIを実装します。
今回実装したコードは以下のようになっております。記述するファイルはlib/uiのpicker_pageファイル(lib/ui/picker_page.dart)です。
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:picker_project/controller/picker_page_controller.dart';
class PickerPage extends ConsumerWidget {
const PickerPage({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final imageFile = ref.watch(pickerPageProvider.select((s) => s.imageFile));
return Scaffold(
appBar: AppBar(
title: const Text('image_picker'),
centerTitle: true,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
children: [
CircleAvatar(
radius: 120,
backgroundColor: Colors.black12,
child: CircleAvatar(
radius: 118,
//もしimageFile(image_pickerで選択した画像)があれば、それを表示
//無ければ、別の画像を表示
backgroundImage: imageFile != null
? Image.file(imageFile, fit: BoxFit.cover).image
: const AssetImage('assets/images/profile.jpg'),
),
),
//RawMaterialButtonでCircleAvatarをtapできるようにしている
RawMaterialButton(
onPressed: () async {
final image = await ImagePicker()
.pickImage(source: ImageSource.gallery);
await ref
.read(pickerPageProvider.notifier)
.pickImage(image);
},
child: const SizedBox(
width: 240,
height: 240,
),
shape: const CircleBorder(),
elevation: 0,
),
],
),
const SizedBox(height: 5),
const Text(
'タップして画像を選択しよう',
style: TextStyle(fontSize: 15),
),
],
),
),
);
}
}
image_picker
で画像を選択されていない時にCircleAvatar
で表示する画像の準備をします。image_picker
で画像を選択していない時の画面UIは以下の画像のようになっています(画像は好きなものを用意してください)。
先ずは、プロジェクト内に画像ファイルを用意します。用意するファイルはプロジェクト直下(libフォルダーと同じ階層)にassetsフォルダーを用意して、その中のimagesフォルダー内(プロジェクト名/assets/images/画像名)に用意します。
次に、先ほど用意したファイルをプロジェクト内でpathを通します。やり方は、pubspec.yaml
ファイルのdev_dependencies
に以下のように記述するだけです。
dev_dependencies:
flutter:
assets:
- assets/images/画像のファイル名 (拡張子の.jpgなども忘れずに記入!)
記述し終わると再度、画面上のpub get
ボタン、もしくはターミナルでflutter pub get
を入力して実行します。
メッセージやターミナルでエラーが吐かれなければ、プロジェクト内に画像ファイルのpathが通ったことになります。
これで、image_pickerで画像を選択していない時に表示する画像を用意することができました。
UIの画面が完成したので、次にriverpodを利用するために状態管理をするcontroller
側の記述をしていきます。
まず、picker_page_controller.dartファイル(lib/controller/picker_page_contorller.dart)を用意します。そして、picker_page_controller.dartファイルには以下のように記述していきます。
import 'dart:io';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart';
part 'picker_page_controller.freezed.dart';
class PickerPageState with _$PickerPageState {
const factory PickerPageState({
File? imageFile,
}) = _PickerPageState;
}
final pickerPageProvider =
StateNotifierProvider.autoDispose<PickerPageController, PickerPageState>(
(ref) {
return PickerPageController();
});
class PickerPageController extends StateNotifier<PickerPageState> {
PickerPageController() : super(const PickerPageState());
Future<void> pickImage(XFile? image) async {
if (image == null) return;
state = state.copyWith(imageFile: File(image.path));
}
}
@freezed
の部分でPickerPageState
をimmutableなクラスにしています。part:〜
の部分はpart:
以降はcontroller側の記述をするファイル名をそのまま記述し、その後に.freezed.dart
を付け足すと言う感じで記述します。
(例) test_contorller.dartであれば、以下のように記述します。
part 'test_controller.freezed.dart';
このように書くのは、freezed
のパッケージがそのような仕様になっているためであるのでこんな風に書くんだなと思ってもらえればと思います。
上記のようにpicker_page_controller.dartファイル内に記述すると、至る所でエラーが出ていると思います。そのため、次にエラーを解消していきます。
まず、ターミナルでflutter packages pub run build_runner build
を実行します。Succeededと表示されるとpicker_page_controller.freezed.dartファイルが作成され、出ていたエラーが解消されたと思います。
※Conflicting outputs were detected and the build is unable to prompt for permission to remove them 〜
と言うエラーが生じた場合は、ターミナルでflutter packages pub run build_runner build --delete-conflicting-outputs
を実行するとエラーが解消されると思います。
これでプロジェクトを実行するとエラーが出ずにプロジェクトが立ち上がったと思います。
プロジェクトが立ち上がればCircleAvatar
(円の部分)をタップするとimage_pickerが呼び出されて以下の画像のような画面に遷移すると思います。
画像の左上のハンバーガーメニューを押して、Downloadsを押します。そして、Downloadsの画面に表示したい画像をdrag&dropすると先ほどdrag&dropした画像が表示され、それを選択すると、プロジェクトのCircleAvatar
の部分に選択した画像が表示されたと思います。
いかがでしたでしょうか。
riverpodを使ってimage_picker
の実装を紹介しました。image_picker
を使用すればアプリ内によくあるプロフィールの編集ページなどを実装することができ、作るアプリのクオリティーを一気に上げることができると思います。
私自身もまだまだriverpodについて理解できていない部分がございますので、もしこの記事内で間違った部分等がありましたらご連絡して頂けたら幸いです。
https://qiita.com/karamage/items/4b1aff984b1af7541b73#:~:text=%E3%80%8Cstate_notifier%E3%80%8D%E3%81%AF%E3%80%81provider%E3%81%A8,%E3%81%9F%E3%82%8A%E3%81%97%E3%81%A6%E3%81%8F%E3%82%8C%E3%81%BE%E3%81%99%E3%80%82 https://minpro.net/conflicting-outputs-were-detected-and-the-build-is-unable https://note.com/mxiskw/n/n5c06bc2dd0d5 https://note.com/hatchoutschool/n/nce574251aaae
可茂IT塾ではFlutterインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。
Read More可茂IT塾ではFlutterインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。
Read More