2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WebAPIと連携してフリー画像検索アプリ制作チュートリアルを学びました。の続編。備忘録です

Posted at

下記動画を参考に作りました。
【Flutterの教科書】⑤ WebAPIと連携してフリー画像検索アプリを作ろう【後編】
https://youtu.be/S8AEyv2cKL8?si=1jb3v9J8OOqwgGFU

いつものように目次に沿ってメモを残します。

00:00 検索する画像を変更する方法

検索可能にするには、テキストを入力してそれをこの画像を表示させるってところ実装していかなければなりません
入力フィールドを作る前に、この画像一覧をどうやったら変更できるのかやっていきます

01:40 クエリパラメーターの解説

まずはgetしているURLに着目する
眺めていると、yellow+flowersと書いてあるところがある。

Response response = await Di
o().get('https://pixabay.com/api/?key=42370728-d42399baa5e5ae716911d91c4&q=yellow+flowers&image_type=photo&pretty=true';

どうやらこれが怪しいなと。ここをちゃんと変えたら画像が変わるのか。を確認
直に該当箇所を書き換えたら、ちゃんと変更された。日本語文字列も対応してくれるようだ
こういう情報は、観察と仮説を立てて実験してもいいですけど、大抵は公式のドキュメントに書いてある
そのうちのParametersという項目がある。ここでどういうふうにパラメータを与えていけいいのかが書いている

URLは?の後ろからパラメータが複数書かれている。~=~で1つのパラメータ、&で区切られている
試しにパラメータを追加します。デフォルトだと20件しか取れないので、もっと取れるようにします
件数のパラメータは公式だとper_pageとあったのでここで100件表示できるように追加します
クエリパラメータはこの様になっていました

02:27 TextFormFieldの追加

次はURL中のクエリパラメータの該当箇所を変更できるよう実装していきます
まずは入力フィールドを作らなきゃいけないので作っていきます
入力フィールドはどこでもいいですが、今回はappberに作ります
TextFormFieldというのを追加すると入力可能なフィールドが追加される

      appBar: AppBar(
        title: TextFormField(),
      ),

アプリ上で文字を入力しようとする日本語が打てない
その場合は、そのエミュレーター上で言語の設定を変えればいい(※言語を追加することになるが、日本語の順番を一番上にすること)
入力フィールドが見づらいので、装飾する

        title: TextFormField(
          decoration: InputDecoration(
            fillColor: Colors.white,//入力項目の背景色を白にする
            filled: true,
          ),

ここに入力した値をどうやって取得するかというとonFieldSubmittedを使っていきます

          onFieldSubmitted: (text){

このtextという変数の中には、アプリ上の入力フォームに入力している文字という値が入ってきます
これをプリントしてみましょう。そうするとアプリで入力して決定→コンソール上に入力した文字が返される
なので、アプリ上で決定したときに実行されるので、そのタイミングでfetchimagesを呼び出せば良いということになります

04:52 onFieldSubmittedで関数を実行

今引数を与えられないようになっちゃっていますのでまずは引数を与えられるようにしましょう

変更前

  Future<void> fetchimages() async {

変更後

Future<void> fetchimages(String text) async {

型はStringなのでStringと、変数名はなんでもいいですけど、textとしました
このtextを、このURLに組み込めばいい。組み込むところはさきほどの該当箇所を変える
ここでtextを評価したいんですけど
ここに単純にtextと書くと、値は単純な文字列として認識されてしまうので
これを回避するために、$マークというのを追加すればOKです。URLに「&q=$text」てな感じ
このtextは変数と解釈され、この変数の中身が展開されてこの文字列に合体して評価されます

これで引数を与えられるようにしたので
現状だとfetchimages();にエラーが出ます
これ消してもいいけど、初回に何かを表示させたい場合は、初夏に表示させたいキーワードを入力しておくことができます
変更後例:fetchimages('車');

ユーザーにわかりやすくするために、initialValueというものがあって、ここに文字列を入れておくと、ユーザーに読ませるこtができる(入力フォームのところ)
onFieldSubmittedが実行されるときにですね、fetchimagesもあわせて実行しましょうということです

        title: TextFormField(
          initialValue: 'ここに検索したいワードを入力して下さい',
          onFieldSubmitted: (text) {
            print(text);//これは今後いらないから削除する
            fetchimages(text);
          },

これで、好きなキーワードを入力して好きな画像を検索できるところまできました

06:43 Stackを使ったいいね数の表示

次は見た目の方を触っていきます。
まず、hitのデータの中には「いいね」数というのも入っている
そのいいね数を表示させるをやります。
各画像の上に重ねるように表示させたいので、stackというのを使います。スタックの候補は出てこないので
colunuかrowで該当箇所を囲みます。ctrl + . で対象を囲む

return Image.network(hit['previewURL']);

を、下記のように囲む

          return Stack(
            children: [
              Image.network(hit['previewURL']),
              //いいね数。keyの名前がlikes
              //これを文字列として評価したい場合は上記のように${}で囲む。
              Text('${hit['likes']}'),
            ],
          );

07:48 Containerを使って背景色を追加

左上に数字が表示されました。ただ見えづらいので背景色をつけていきます。色々方法はありますが
今回はconeainerを使います。これにはcolorというプロパティがあるのでこれを使います

Container(color: Colors.white, child: Text('${hit['likes']}')),

08:05 見た目の修正 StackFit, BoxFit

現状縦横比がバラバラなので、正方形にきれいにします。Stackのすぐ下に下記のものを入れます

          return Stack(
            fit: StackFit.expand,

これだとコンテナーごとfitされておかしくなるので、もろもろ調整します。

          return Stack(
            fit: StackFit.expand,
            children: [
              Image.network(
                hit['previewURL'],
                fit: BoxFit.cover, //画像の比率をcoverで表示できる
              ),
              //いいね数。keyの名前がlikes
              //これを文字列として評価したい場合は上記のように${}で囲む。
              Align(
                  //整列
                  alignment: Alignment
                      .bottomRight, //文字を右下に移動。bottomRightは変えられるので位置は好きなところにおける
                  child: Container(
                    color: Colors.white,
                    child: Row(
                      //横並び
                      mainAxisSize: MainAxisSize.min, //これを与えると横幅いっぱいがちっちゃくできる
                      children: [
                        const Icon(
                          Icons.thumb_up_alt_outlined, //アイコンの追加
                          size: 14, //アイコンのサイズ
                        ),
                        Text('${hit['likes']}'),
                      ],
                    ),
                  )),
            ],
          );

調整大変…

10:40 InkWellを使って画像をタップ可能にする

次はシェア機能の追加です。タップできるようにする。
タップするにも色々な方法がありますが、今回はInkWellというWidgetを使っていきます
StackをWidgetで囲って(ラップ)InkWellに変更。そしてその中にontapというものを書くことによってタップ可能になる
とりあえずタップできるか確認するのにprint(hit['likes']);してみます

          return InkWell(
            onTap: () {
              print(hit['likes']);
            },
            child: Stack(

11:34 画像を共有する機能の実装

コンソールでライク数が表示されました。タップ可能になりました。
このタイミングで、画像を共有するように実装する。ただこれは少々手間。
手順としてまして

//1.URLから画像をダウンロード
//2.ダウンロードしたデータをファイルに保存
//3.Shareパッケージを呼び出して共有

パッケージを追加する方法は前にもやった通り、ターミナル上でflutter pub addする
今回はpath_providerが必要なのでこれを追加します。

flutter pub add path_provider

次はshare_plusこれがShare機能追加するパッケージになります

flutter pub add share_plus

追加されたか確認したい場合は、pubspec.yamlファイルを確認する
これに追加されていれば成功です。
追加したものが反映されないことがあるので、一度■ボタンで停止します。

これで準備が整ったので、上記の1,2,3をやっていきます。

URLから画像をダウンロードするときは、dioのパッケージをそのまま使っていきます。
dioに対して、get ここまでは一緒
今回このpathのURLについてはこのwebFormatURLっていうのが比較的きれいな画像らしいのでこのURLを指定します
hitに対してこのwebFormatURLを指定する。この中にはURLが入っている
これではまだ完結しておらず、optinsというのを渡す必要があります。optionsに対してresponseTypeそしてbytesを指定することで
画像データを取得することができるようになります。
dioを受け取らないといけないのでレスポンスに代入する。
Futureなのでontapのところでasync、responseのところでawaitとかく

            onTap: () async {
              //1.URLから画像をダウンロード
              //2.ダウンロードしたデータをファイルに保存
              //3.Shareパッケージを呼び出して共有
              Response response = await Dio().get(
                hit['webFormatURL'],
                options: Options(responseType: ResponseType.bytes),
              );
            },

これで1はOKです
次は2.ディレクトリという型がありまして、これで保存一時的に保存可能な領域のフォルダーのパス情報を取得することができます

Directory dir = await getTemporaryDirectory();//これもFutureがついているのでawait

これで一時的に保存可能な領域のパスデータを取得することができます
あとは、
レスポンスに画像データがありますので、これを書き込んで上げることをします
今はパスを取得したとこまできたので、次は
fileという変数を作ってあげて
ディレクトリのパスがあるので、ディレクトリのパスに対してスラッシュで区切る
/image.png'のところのfile名は適当で良い
これで書き込みができます。これもawaitで待ってあげましょう

              File file = await File(dir.path + '/image.png').writeAsBytes(response.data);

この1行でimage.pngというfileに、さっきダウンロードしてきたbyteデータを書き込むという処理をやっています
書き込んだらこの上記の
file に、実際書き込まれたデータが入ってくる

次に3.これはパッケージのちから様様でかんたんに書ける。ShareというのがあってSharefilesというのがあります
Listの形にしたいので[]をつけたうえでfileのパスを指定するだけです。

Share.shareFiles([file.path]);

これでShareが可能になります。
保存したfileのパスを、shareFilesに与えた上げると、Shareが可能になる

ビルドエラーに遭遇…

と思ったら、ビルドエラーに遭遇…
エラー内容を見ると、JDKの依存関係でビルドエラーが起きているよう、
初心者にはきついよと思いつつ検索したら、とても良い記事を見つけ解決することができました。
有難うございます!!

参考
【Android】JDKの依存関係でビルドエラーとなった場合の対処法
https://qiita.com/YuukiYoshida/items/37dd7cea0918fe8a03d6

17:25 このチャプターの振り返り 19:42 次回予告

最後にこのチャプターで学んだトピックをおさらいします。

ドキュメントを参考にクエリパラメータを変更することで欲しいデータが得られる
TextFormField の見た目は InputDecoration で変更できる
onFieldSubmitted で入力した文字列を取得できる
Stack で Widget を重ねられる
InkWell でラップすると onTap で選択時に呼び出す関数を指定できる
path_provider で端末の保存可能なフォルダの位置を取得できる
share_plus でダウンロードした画像を共有できる
理解度はどうでしょうか。わからないトピックがあればもう一度読み返してみてください。

次回のチャプターでは機能はそのままにコードの質を高める リファクタリング を行います。実は今まで書いてきたコードはあまり>よいものとは言えません。もっと読みやすく、安全で、使いやすいコードにするための修正過程を解説します!

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?