CodeZine(コードジン)

特集ページ一覧

Flutterでのデータ連携・処理について解説!~非同期処理と画面の連携機能を作ろう~

Flutterで始めるモバイルアプリ開発 第21回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2022/07/14 11:00

 今回からは、データ連携・処理について説明していきます。しかし、データ連係・処理と単純に言っても少々わかりにくく、また、画面内で処理を実装してもうまく動かない、または、処理中の利用者の操作などで問題が生じるケースがあります。Flutterアプリでは常に画面処理が動いているために、データ処理であっても画面側に影響しない、もしくは影響を及ぼしていることをアプリ利用者に把握してもらうような実装をする必要があります。本稿では、アプリ内でデータ連携・処理をする場合のイメージがつきやすいように、サンプルアプリを通じて、Flutterでのデータ連携・処理について紹介します。

目次

サンプルアプリケーションの概要

 今回のサンプルアプリケーションは、ログイン画面での認証を想定したもので、図1になります。

図1:認証画面のサンプルアプリ
図1:認証画面のサンプルアプリ

 今回のサンプルアプリのように、起動時にアプリのバージョン情報や前回の状態を考慮した処理を行うことがあります。そこでこのアプリでも、画面初期化の部分で設定取得を想定したGETリクエストを行います。

 そして、IDとパスワード入力でのログイン処理をPOSTリクエストで行うことを想定しています。また、それぞれのサーバへのリクエスト中はローディング中を示す画面を表示するようにします。

 なお、それぞれのタイミングで発生するサーバへのリクエストでは、セキュリティを考慮したリクエストヘッダなどの設定が必要になることでしょう。本サンプルではそのようなケースを想定したタイミングで処理を実装しています。

実装の流れ

 今回と次回を通じて以下の流れで進めていきます。

 1. データ連係部分でモック機能のみ(画面処理を確認する方法)

 2. http通信を使った場合のデータ連携(基本的な使い方を用いた連携方法)

 3. http通信を使った場合のデータ連係(ヘッダなどのカスタマイズなどより実利用を想定した連携方法)

 今回は、1. のモックを使った画面処理を確認できるようにし、次回は実際にhttpを使ったアクセス方法を紹介します。

画面側のプログラム

 まず、ログイン画面側のプログラムを紹介します。これまで紹介してきた部分を含めてUI部品については表示を省略しています。

 全体のコードはサンプルコード内にあるので、そちらを参照してください。また、各部品についてわからない部分があれば、これまでの紹介記事を参考にしてもらえればと思います。

[リスト1]ログイン画面のコード例(lib/page/LoginPage.dartの抜粋)
class LoginPage extends StatefulWidget{
    // : (省略)
}

class _LoginPage extends State{

    // : (省略)
    final _loginIdFocus = FocusNode();
    final _pswdFocus = FocusNode();

    //  (1) このページでデータ取得とログイン処理などをするためのクラス
    final ILoginController _pageController = MockLoginController();
    //  (2) 初期データ読みこみ完了フラグ
    var _init = false;
    //  (3) ログインリクエストの読み込み中フラグ
    var _loading = false;

    @override
    Widget build(BuildContext context) {

        return Scaffold(
            body: Stack(
                children: [
                    Form(
                        // : (省略)
                    ),
                    // (4) 読込中の場合には、画面の上にローディング画面を表示
                    if(!_init) createLoadingGlass(context),
                    if(_loading) createLoadingGlass(context),
                ],
            )
        );
    }
    @override
    void initState() {
        // (5) 初期データを読みこむ
        var f = _pageController.config();
        f.then((value) => setState((){
            _init = true;
        }));
        super.initState();
    }
    // : (省略)

    // (6) ローディングを示す画面を作成する
    Widget createLoadingGlass(BuildContext context){
        return Container(
            // : (省略)
            child: Center(
                child: CircularProgressIndicator()
            )
        );
    }
    // : (省略)
    Widget createLoginForm(BuildContext context){
        return Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
                TextFormField(
                    // : (省略)
                ),
                TextFormField(
                    // : (省略)
                ),
                ElevatedButton(
                    onPressed: () async {
                        final ok = _formState.currentState!.validate();
                        if(ok){
                            // : (省略)

                            // (7) フォーカスを外す(キーボードを閉じる)
                            _loginIdFocus.unfocus();
                            _pswdFocus.unfocus();

                            // (8) ローディング画面を表示
                            setState(() {
                                _loading = true;
                            });
                            // (9) サーバ側にリクエストする
                            try {
                                Response res = await _pageController.login(
                                    _loginIdController.text, _pswdController.text);

                                // : (省略)
                            } on Error catch(e){
                                showErrorSnackBar(context, '不明なエラーが発生しました');
                            } finally{
                                setState(() {
                                    _loading = false;
                                });
                            }
                        }
                    }, child: Text('ログイン'))
            ],
        );
    }
    // : (省略)
}

 (1)は、データ取得とログイン処理を行う部分のクラスです。このクラスと実装については、あとで説明します。

 (2)、(3)はサーバへのリクエスト中にローディング画面を表示するためのフラグ管理をするための変数で、(4)のようにStackレイアウトで画面上部に表示されるようにしています。

 (5)が画面の初期化時にサーバへ初期設定を読みこむことを想定した処理になります。この時、画面側で注意しなければいけない点があります。それは、リスト2のようにメソッドにasyncキーワードをつけて書き換えられないということです。従って、initStateなどではthenなどを使った非同期処理が必要になります。

[リスト2]initState()でasyncを使った書き換えはできない
void initState() async{
    _pageController.config();
    setState(() {
        _init = true;
    });
    super.initState();
}

 (6)がローディング画面の表示です。これまで紹介していませんでしたが、CircularProgressIndicatorがローディング中に表示されるアニメーション部分のクラスです。

 このローディング表示には、進捗割合を表す表示方法と何らかの処理が実行中であることだけを示す方法の2種類が用意されていますが、今回は、後者の何らかの処理が実施している状況を示すだけの表示を採用しています。

 そして、(7)では、入力フィールドからフォーカスを外しています。これは、キーボードが表示されたままだと、実際には入力ができるようになってしまうので、その抑制をしています。続いて、(8)でローディング画面を表示し、(9)で実際にログイン時の処理を行っています。

 これまでの連載ではおもにデータ処理を伴わない場合の画面のみを扱ってきましたが、データ処理を扱うと画面側でも考慮しなければならないことが増えるということが実感できたと思います。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:Flutterで始めるモバイルアプリ開発

もっと読む

著者プロフィール

  • WINGSプロジェクト 小林 昌弘(コバヤシ マサヒロ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。個人紹介主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

あなたにオススメ

All contents copyright © 2005-2022 Shoeisha Co., Ltd. All rights reserved. ver.1.5