20210105のAndroidに関する記事は9件です。

【iOS/Android】ネイティブアプリをAPIと繋ぎ込む時に便利なツール

iOS/AndroidでAPIをつなぐ時に便利なツールを紹介します!

Chrome拡張のTalend API TesterでAPIテスト - Qiita

APIが正常に動いているか、これでチェックします!
この記事わかりやすくて神ですよ!

Text Escaping and Unescaping in JavaScript

JSONに入っているUniCode、読みたいですよね?
そんな時はこれに打ち込めば!日本語に変換してくれて便利!!!

JSON Schema Tool

あとは最後に、これ
JSONを入れるだけでスキーマを作ってくれます!楽チンですね

まとめ

この記事で少しでも開発が捗ったら嬉しいです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

InheritedWidgetの目的と使い方【Flutter】

まえがき

InheritedWidgetの使い方の基本をまとめます!

Flutterを勉強していてよくわからなくなるポイントの一つがこのInheritedWidgetだと思います。

筆者自身、これを理解するのにかなり時間がかかったので、「こういう説明があったらよかったな」と思う説明を書きます。

なお、以下では、InheritedWidgetクラスを継承したクラスのことを単にInheritedWidgetと呼びますね。他のStatefulWidgetなども同様です。

前提知識

FlutterアプリがWidgetのツリー構造でできていることを知っていて、StatefulWidgetsetStateは使えるものとします。

BuildContextの知識も少しだけ要りますが、「Widgetツリー内での位置を表現するもの」程度の理解で十分です。

それについてはこちらをお読みください。

Flutterのcontextの「お気持ち」を理解する

記事内の例の実行方法

該当WidgetをMaterialAppで包んでrunAppに渡すか、GitHubのrepositoryからプロジェクトをダウンロードして実行してください。

それぞれの例がページ別にわかれたアプリになっています。

https://github.com/agajo/learn_inherited_widget

InheritedWidgetの存在理由

InheritedWidgetの目的は、次の2つです。

  • Widgetツリーの下層のBuildContextから、プロパティにO(1)でアクセスすること
  • プロパティが変化した時に下層のBuildContextに更新を伝えてリビルドさせること

一つずつ説明します。

Widgetツリーの下層のBuildContextから、プロパティにO(1)でアクセスする

このOはランダウの記号と言います。ゼロではなくてオーです。

O(1)というのはアクセスにかかる時間が何に依存して増えるかを表す表現なのですが、O(1)ということは、そのアクセス時間が何にも依存しない(つまり増えない。常に最速でアクセスできる)ことになります。

ランダウの記号#無限大における漸近挙動と計算量の見積り - Wikipedia

dependOnInheritedWidgetOfExactTypeを使え!

BuildContextにはdependOnInheritedWidgetOfExactTypeというメソッドがあります。

context.dependOnInheritedWidgetOfExactType<Hoge>()のように書くと、そのcontextからWidgetツリーをさかのぼった時に一番近くにあるHogeというInheritedWidgetを返します。

その時の計算時間はO(1)です。つまり最速です。

基本的にfindAncestorStateOfTypeは使うな!

BuildContextにはfindAncestorStateOfTypeというメソッドもあり、同じようにBuildContextからWidgetツリーをさかのぼって一番近くにある、指定した型のStateを返すのですが、その時の計算時間はO(n)です。

このnBuildContextから実際に返るStateまでの、Widgetツリーの深さを表します。つまりWidgetツリーが深くなると、それに比例して計算に時間がかかるようになり、アプリが重くなっていくのです。

使用例

InheritedWidgetは、StatelessWidgetStatefulWidgetと同様に、InheritedWidgetクラスをextendsしたクラスを自分で作って使います。

この例ではMessageDataというInheritedWidgetを作っています。

updateShouldNotifyについては後ほど説明しますので今はスルーしてください。

下記のMainWidget()を、MaterialAppで覆ってrunAppに渡します。

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

// InheritedWidgetを作ります。
class MessageData extends InheritedWidget {
  const MessageData({Key key, @required Widget child})
      : assert(child != null),
        super(key: key, child: child);

  // InheritedWidgetのプロパティは変更不可能なので必ずfinal
  final String message = 'InheritedWidgetが保持してるメッセージだよ〜〜〜〜';

  // updateShouldNotifyについては後で説明します。
  @override
  bool updateShouldNotify(MessageData old) {
    return true;
  }
}

class MainWidget extends StatelessWidget {
  const MainWidget();
  @override
  Widget build(BuildContext context) {
    // Widgetツリーの上層部に、作ったInheritedWidgetを配置
    return MessageData(
        child: Scaffold(
      appBar: AppBar(),
      body: WidgetA(),
    ));
  }
}

class WidgetA extends StatelessWidget {
  const WidgetA();
  @override
  Widget build(BuildContext context) {
    // InheritedWidgetからO(1)でmessageを取得!!
    final message =
        context.dependOnInheritedWidgetOfExactType<MessageData>().message;
    return Text(message);
  }
}

WidgetA内で、上層のInheritedWidgetからmessageが取得され、表示されます。

スクリーンショット 2021-01-03 0.18.30.png

Widgetツリー内でInheritedWidgetを上書きする

Widgetツリー内の一部でだけ異なるMessageDataを使いたい時は、もう一度MessageDataWidgetを配置すればいいです。

Theme, MediaQuery, Directionalityなど多くのFlutter Widgetがこの性質を持っていますね。実はそこでもInheritedWidgetが利用されています。

次の例を見てください。

MessageDataは2箇所配置されていますが、より呼び出し元のBuildContextに近い方からmessageが取得されていることを確認してください。

MessageDataのプロパティmessageはコンストラクタで外から受け取れるようにしました。

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

// InheritedWidgetを作ります。
class MessageData extends InheritedWidget {
  const MessageData({
    Key key,
    @required Widget child,
    @required this.message,
  })  : assert(child != null),
        super(key: key, child: child);

  // 今回はmessageをコンストラクタで受け取ります。
  final String message;

  @override
  bool updateShouldNotify(MessageData old) {
    return true;
  }
}

class MainWidget extends StatelessWidget {
  const MainWidget();
  @override
  Widget build(BuildContext context) {
    return MessageData(
        message: 'メッセージその1だよ',
        child: Scaffold(
          appBar: AppBar(),
          // 内容を変えたMessageDataウィジェットを挟み込みます
          body: MessageData(
            message: 'メッセージその2ですよ〜〜〜',
            child: WidgetA(),
          ),
        ));
  }
}

class WidgetA extends StatelessWidget {
  const WidgetA();
  @override
  Widget build(BuildContext context) {
    final message =
        context.dependOnInheritedWidgetOfExactType<MessageData>().message;
    return Text(message);
  }
}

スクリーンショット 2021-01-03 0.33.16.png

プロパティが変化した時に下層のBuildContextに更新を伝えてリビルドさせる

InheritedWidgetのインスタンスは、中身を変更できません。

にもかかわらず更新を伝播するとは、一体どういうことなのでしょう?変更不可能なのに更新するとは??

筆者は以前、この部分で混乱していました。

答えはこうです。

InheritedWidgetのインスタンスを何度も作り直して置き換える

状態を更新可能なStatefulWidgetなどと組み合わせて、Widgetツリー内にあるInheritedWidgetのインスタンスを作り直して再配置します。

プロパティの異なるInheritedWidgetを作って置き直せば、プロパティの変更が、それに依存するBuildContextに伝播され、リビルドされるわけです。

使用例

実際の例で見てみましょう。

StatefulWidgetを使って、1秒に1回、プロパティを1ずつ増やしたInheritedWidgetを作って再配置します。作るInheritedWidgetの名前はCountDataとしています。

InheritedWidgetchildに渡すWidgetは、変更せず、同じインスタンスを毎回渡すことにします。

update_count.dart
import 'dart:async';

import 'package:flutter/material.dart';

// InheritedWidgetはmessageではなくcountを保持することにします。
class CountData extends InheritedWidget {
  const CountData({Key key, @required Widget child, @required this.count})
      : assert(child != null),
        super(key: key, child: child);

  final int count;

  @override
  bool updateShouldNotify(CountData old) {
    return true;
  }
}

// StatefulWidgetを使います。
class MainWidget extends StatefulWidget {
  MainWidget();
  // InheritedWidgetのchildには、毎回このインスタンスを渡します。
  final Widget child = Scaffold(
    appBar: AppBar(),
    body: WidgetA(),
  );

  @override
  _MainWidgetState createState() => _MainWidgetState();
}

class _MainWidgetState extends State<MainWidget> {
  int timeCount = 0;
  Timer timer;

  @override
  void initState() {
    super.initState();
    // Stateの初期化時に、毎秒setStateを呼ぶタイマーを起動します。
    timer = Timer.periodic(Duration(seconds: 1), (timer) {
      setState(() {
        timeCount++;
      });
    });
  }

  @override
  void dispose() {
    // initStateでタイマーを起動・disposeでタイマーを破棄。この2つはセットです。
    timer.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // タイマーにより毎秒setStateが呼ばれるので、このbuildメソッドが毎秒呼ばれます。
    // その際、このCountDataコンストラクタでCountDataのインスタンスが毎回作り直されます。
    // 渡す引数が異なるので、作られるCountDataのプロパティは毎回異なります。
    return CountData(
      count: timeCount,
      // StateではなくStatefulWidgetが保持しているchildを渡します。
      // つまり、毎秒変更されるインスタンスはCountDataだけであり、他は変化しません。
      child: widget.child,
    );
  }
}

class WidgetA extends StatelessWidget {
  const WidgetA();
  @override
  Widget build(BuildContext context) {
    // ここでこのWidgetAのBuildContextがCountDataの変更通知対象として登録されるので、
    // CountDataのインスタンスが置き換わるたびにこのbuildメソッドが呼ばれます。
    final count = context.dependOnInheritedWidgetOfExactType<CountData>().count;
    return Text('count: $count');
  }
}

Jan-03-2021 20-36-01

updateShouldNotifyで更新を間引きする

InheritedWidgetのよくわからないポイントの一つ、updateShouldNotifyです。

ここまでの説明で、InheritedWidgetのインスタンスの置き換えが行われる際に更新が伝播されるようなことを言いましたが、

実は、実際に更新が伝播されるのはupdateShouldNotifytrueを返した時だけです。

インスタンスの置き換えの際、古い方のインスタンスも手元に手に入るので、比較して、更新通知するか制御可能です。

例えば先程の例で、更新を2回に1回にする(つまり、countを2で割った商が変化した時だけ更新する)場合、updateShouldNotifyは次のように書けます。

  @override
  bool updateShouldNotify(CountData old) {
    return count ~/ 2 != old.count ~/ 2;
  }

他は先程の例と全く同じようにすると、次のような動きになります。

Jan-03-2021 20-40-54

updateShouldNotifyの中身を、場合によって都合のいいように実装すればいいわけですね!

慣例として、ofを実装する

ここまで、動きの理解を重視して、BuildContext.dependOnInheritedWidgetOfExactTypeを直接呼んでいましたが、

Flutterの慣例として、このメソッドは直接呼ばず、InheritedWidgetofというstaticメソッドを実装して、そこから呼ぶのが普通です。

最初のMessageDataの例ですと、このようになります。

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

class MessageData extends InheritedWidget {
  const MessageData({Key key, @required Widget child})
      : assert(child != null),
        super(key: key, child: child);

  final String message = 'InheritedWidgetが保持してるメッセージだよ〜〜〜〜';

  @override
  bool updateShouldNotify(MessageData old) {
    return true;
  }

  // ofを実装します!!!
  static MessageData of(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType<MessageData>();
}

class MainWidget extends StatelessWidget {
  const MainWidget();
  @override
  Widget build(BuildContext context) {
    return MessageData(
        child: Scaffold(
      appBar: AppBar(),
      body: WidgetA(),
    ));
  }
}

class WidgetA extends StatelessWidget {
  const WidgetA();
  @override
  Widget build(BuildContext context) {
    // of経由で呼びます!!!
    // final message = context.dependOnInheritedWidgetOfExactType<MessageData>().message;
    final message = MessageData.of(context).message;
    return Text(message);
  }
}

こうすると、Themeなどで見かけたTheme.of(context)という見た目にそっくりになりましたね。

以上でInheritedWidgetの基本的な使い方の説明を終わります。

(応用)下層から更新する場合はどうするの?

InheritedWidgetを、アプリの状態管理にガッツリ使うことを考えます。

先程のタイマーを使った例では、更新がStatefulWidgetState内部から自発的に発生していました。

一方実際のアプリでは、Widgetツリー下層にあるRaisedButtonが押されたら、上層で保持している状態を更新する、という動きがよくあります。

となると、Widgetツリーの下層から上層の状態を更新するメソッドを呼ぶ必要があるわけです。

下層から更新用メソッドにアクセスできるようにする方法は、例えば

  • その更新用メソッドを、毎回Widgetのコンストラクタに渡して下層まで持っていく

という方法がありますが、これは中間層すべてのコンストラクタに記述が必要で、イマイチです。

せっかくInheritedWidgetがあるので、更新用メソッドにも下層からO(1)でアクセスしましょう!

今回は、公式のList of state management approachesからリンクされている

これらを参考に、一つ方法を紹介します。

InheritedWidgetState自体を下層に流せ!

StatefulWidgetを使う場合、プロパティも更新用メソッドもStateが持っていることが多いです。更新時にsetStateを呼ばないといけないので、更新用メソッドもStateが持たないといけない。

ならば、State自体をInheritedWidgetで下層に流してしまえば、そのプロパティにアクセスするのも更新用メソッドにアクセスするのも簡単ですね!

その方法を簡単にまとめるとこうです。

  • StatefulWidgetInheritedWidgetのペアで使う
  • InheritedWidgetprivateにする
  • Statepublicにする
  • InheritedWidgetdataというプロパティを持ち、Stateを格納する
  • ofメソッドはStatefulWidgetが持ち、State(InheritedWidgetdataプロパティ)を返す
  • StatefulWidgetchildを受け取り、StatebuildInheritedWidgetの直下に入れて返す
  • メソッドだけ欲しい場合はBuildContext.getElementForInheritedWidgetOfExactTypeを使うと、更新伝播の対象にならない

実例でコメントと一緒に見るのが早いと思いますので、次をご覧ください。

使用例

ボタンが押された回数をテキストで表示するだけのアプリを考えます。

ただし、状態管理WidgetはWidgetツリーの上層にあり、ボタンとテキストはツリーの違う枝に置きます。

今回は状態更新用WidgetとアプリUI用Widgetでファイルを分けます。

countを管理するStatefulWidget+InheritedWidgetのセット

まず、状態管理用のWidgetを用意します。

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

// InheritedWidgetはStateを保持するだけの役。
// privateにします。
class _StateContainer extends InheritedWidget {
  const _StateContainer({Key key, @required Widget child, this.data})
      : assert(child != null),
        super(key: key, child: child);

  // CountManagerStateのインスタンスを保持し、下層に流します。
  final CountManagerState data;

  @override
  bool updateShouldNotify(_StateContainer old) {
    return true;
  }
}

// 状態管理用Widgetです。
class CountManager extends StatefulWidget {
  const CountManager({this.child});

  final Widget child;

  // ofメソッドはStatefulWidgetが持ちます。
  // ofにlisten引数を追加します。デフォルトはtrueです。
  static CountManagerState of(BuildContext context, {bool listen = true}) =>
      listen
          ? context.dependOnInheritedWidgetOfExactType<_StateContainer>().data
          // listen==falseの場合、このcontextが更新の対象にならないようにします。
          : (context
                  .getElementForInheritedWidgetOfExactType<_StateContainer>()
                  .widget as _StateContainer)
              .data;
  @override
  CountManagerState createState() => CountManagerState();
}

class CountManagerState extends State<CountManager> {
  // 下層からアクセスしたいプロパティです。
  int count = 0;

  // 下層からアクセスしたい状態更新用メソッドです。
  void increment() {
    setState(() {
      count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return _StateContainer(
      // InheritedWidgetのdataに、このStateインスタンス自体を渡す。
      data: this,
      child: widget.child,
    );
  }
}

BuildContextgetElementForInheritedWidgetOfExactTypeは、InheritedWidgetの中身にアクセスしつつも、BuildContextを状態更新時のリビルド対象として登録しません

状態更新用メソッドだけを取得して、リビルド対象にしたくない場合、こちらを使います。

(BuildContextgetElementForInheritedWidgetOfExactTypeの返り値はInheritedWidgetではなくInheritedElementなので、返り値の処理が若干変わってます)

UI構築

update_method.dart
import 'package:flutter/material.dart';
import 'package:learn_inherited_widget/count_manager.dart';

class MainWidget extends StatelessWidget {
  const MainWidget();
  @override
  Widget build(BuildContext context) {
    // CountManagerの傘下に、ボタンと表示用テキストを配置します。
    return CountManager(
      child: Scaffold(
        appBar: AppBar(),
        body: Column(
          children: [
            const ButtonWidget(),
            const ShowCount(),
          ],
        ),
      ),
    );
  }
}

class ButtonWidget extends StatelessWidget {
  const ButtonWidget();
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
        child: const Text('increment!!'),
        onPressed: () {
          // ofメソッドでCountManagerの状態更新用メソッドにアクセスします!
          // ボタンをリビルドする必要はないので、listen:falseとします。
          CountManager.of(context, listen: false).increment();
        });
  }
}

class ShowCount extends StatelessWidget {
  const ShowCount();
  @override
  Widget build(BuildContext context) {
    // ofメソッドでCountManagerのプロパティにアクセスします!
    // 更新を反映する必要があるので、listenはデフォルト(true)にします。
    final count = CountManager.of(context).count;
    return Text('count: $count');
  }
}

Jan-05-2021 22-26-59

Providerパッケージについて

先程の例のcount_manager.dartは50行近くあるのですが、countプロパティとincrementメソッド以外は、毎回ほぼ同じ内容を書くことになります。

そこで、それをパッケージ化すると便利そうだなと思うわけなのですが、

そうして出来上がったパッケージがProviderパッケージです。

(実際には、他にもいろいろな機能が追加されているので単純にそうとは言えませんが)

ということで、基本的にはProviderパッケージを使えばいいと思います。

同じ作者によってさらなる改良がなされたRiverpodもありますね。

もちろん、他にもお好みの状態管理手法を使ってもいいです。

この記事で紹介した方法を使ってもいいと思います。

この記事で紹介した方法は、Flutter以外のパッケージに一切依存しないところがメリットですね。

おわり

おわりです!これでInheritedWidgetを理解してもらえたら嬉しいです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

文字列(リソース名)で画像を取得してImageViewに貼り付ける(kotiln)

以前書いた記事
画像名を文字列で指定してImageViewに貼り付けるコード(android)

これにLGTMしていただいていたので見てみたのですが、微妙だったので書き直します(ついでにkotlinで)
リソース名からImageViewに画像をセットするメソッドは、swiftにはあるんですがkotlinだとありそうでないです。

ちなみにswiftでは

imageView.image = UIImage(named:"hoge.png")

こんな感じでimageViewに画像をセットできます。

同じようなものをkotlinで再現してみます。

resourceIdを文字列から取得

StringExtensions.kt
fun String.getResourceId(context: Context): Int {
    return context.resources.getIdentifier(this, "drawable", context.packageName)
}

Stringの拡張クラスを定義しました

画像をImageViewにセット

ImageViewExtensions.kt
fun ImageView.setImageResourceByName(name: String) {
    this.setImageResource(name.getResourceId(context))
}

ImageViewクラスの拡張を定義しました。

使用例

    private fun setImage() {
        val imageView = ImageView(this)
        imageView.setImageResourceByName("some_image_name_string")
        parent_view.addView(imageView)
    }

parent_viewに動的にImageViewを追加しています

kotlinっぽい書き方をすると

    private fun setImage() {
        parent_view.addView(
            ImageView(this).apply {
                setImageResourceByName("some_image_name_string")
            }
        )
    }

こんな感じでしょうか。
最初は見にくいなと思っていたのですが、「parent_viewに画像を追加する」という責務がひとかたまりで表せるので個人的には気に入っています。

swiftとの比較

swift
imageView.image = UIImage(named:"some_image_name_string.png")
kotlin
imageView.setImageResourceByName("some_image_name_string")

大体似たような感じになったかと思います。
上記で定義したStringとImageViewの二つの拡張をコピペすればそのまま使えます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android アプリ開発時の参考資料集

概要

とあることから、Google Maps APIを使用したAndroidアプリを作成することになったので、
参考にしたリンクを備忘録のために纏めました。

環境編

前提

Androidコードディング規約
もしあなたが急にAndroidアプリを業務で作るはめになった場合の選択肢(2021年初頭版)

Android Studio

Android studio の Gradleについて調べてみた
Android Studioでの外部ライブラリの読み込み方と注意点

ドキュメント

Javadocの作成方法を現役エンジニアが解説【初心者向け】

設計編

Android アプリ設計パターン入門

コーディング編

パーミッション

Androidパーミション別-何してんの?#5 NETWORK編
設定画面のインテントアクションまとめ

画面遷移

[Android] 10分で作る、Navigationによる画面遷移
最初のActivityに戻る
Fragmentによる画面遷移でハマった
Fragmentで、パラメータ付きの画面遷移を実装する

View要素

Button

[Android] Button のonClickListenerの設定が色々できる件

Fragment

Fragment自身を終了させる
AndroidStudioの標準デザインパターンで分かるFragment入門
AndroidでFragmentの結果をActivityで受け取る

HTTP通信

AndroidでHTTP通信したいときの手段まとめ

GPS関連

[Android] GPSで位置情報を取得するアプリを作る

Callback・マルチスレッド・タスク

プロセスとスレッドの概要

Handler

Handler と Looper
Handlerの使い方を紹介する
AndroidでTimerを使わずHandlerだけでお手軽に定期実行してあげる
Handlerクラスの正しい使い方(Androidでスレッド間通信)
幸せな非同期処理ライフを満喫するための基礎から応用まで
【Java】Handlerクラスについてまとめてみました
【Android】HanlderとMessageを使ってマルチスレッドの処理結果を受け取る

Task

【Java】定期的にタスクを実行する方法

スレッドセーフ

排他制御のあれこれ

ライフサイクル

onCreate と onStart と onResume の違い
知らずに作って大丈夫?Androidの基本的なライフサイクルイベント31選
Androidの画面設計や遷移に関して整理してみる

その他

プログレスバー

Androidでプログレスダイアログを使用して、処理を行う
【Android】ProgressDialogがDeprecatedになっている理由、正しく理解してます?

Maps API編

GoogleMapsAPI

Google Maps Platform ドキュメント
地図オブジェクト
AndroidのアプリにGoogle Mapを組み込む
Android - Polyline encoding algorithm?

まとめ

とりあえず、リンクを羅列しましたが、各リンクから何が得られるのか、余裕があり、気力が沸いたら書こうとおもいます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android アプリ開発した際の躓きポイントを纏めてみた

概要

とあることから、Google Maps APIを使用したAndroidアプリを作成することになったので、
参考にしたリンクを備忘録のために纏めました。

環境編

前提

Androidコードディング規約
もしあなたが急にAndroidアプリを業務で作るはめになった場合の選択肢(2021年初頭版)

Android Studio

Android studio の Gradleについて調べてみた
Android Studioでの外部ライブラリの読み込み方と注意点

ドキュメント

Javadocの作成方法を現役エンジニアが解説【初心者向け】

設計編

Android アプリ設計パターン入門

コーディング編

パーミッション

Androidパーミション別-何してんの?#5 NETWORK編
設定画面のインテントアクションまとめ

画面遷移

[Android] 10分で作る、Navigationによる画面遷移
最初のActivityに戻る
Fragmentによる画面遷移でハマった
Fragmentで、パラメータ付きの画面遷移を実装する

View要素

Button

[Android] Button のonClickListenerの設定が色々できる件

Fragment

Fragment自身を終了させる
AndroidStudioの標準デザインパターンで分かるFragment入門
AndroidでFragmentの結果をActivityで受け取る

HTTP通信

AndroidでHTTP通信したいときの手段まとめ

GPS関連

[Android] GPSで位置情報を取得するアプリを作る

Callback・マルチスレッド・タスク

プロセスとスレッドの概要

Handler

Handler と Looper
Handlerの使い方を紹介する
AndroidでTimerを使わずHandlerだけでお手軽に定期実行してあげる
Handlerクラスの正しい使い方(Androidでスレッド間通信)
幸せな非同期処理ライフを満喫するための基礎から応用まで
【Java】Handlerクラスについてまとめてみました
【Android】HanlderとMessageを使ってマルチスレッドの処理結果を受け取る

Task

【Java】定期的にタスクを実行する方法

スレッドセーフ

排他制御のあれこれ

ライフサイクル

onCreate と onStart と onResume の違い
知らずに作って大丈夫?Androidの基本的なライフサイクルイベント31選
Androidの画面設計や遷移に関して整理してみる

その他

プログレスバー

Androidでプログレスダイアログを使用して、処理を行う
【Android】ProgressDialogがDeprecatedになっている理由、正しく理解してます?

Maps API編

GoogleMapsAPI

Google Maps Platform ドキュメント
地図オブジェクト
AndroidのアプリにGoogle Mapを組み込む
Android - Polyline encoding algorithm?

まとめ

とりあえず、リンクを羅列しましたが、各リンクから何が得られるのか、余裕があり、気力が沸いたら書こうとおもいます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Android実機につないでビルドが出来ない時の5つのチェックポイント

「あれ??何で実機が表示されないんだろう?」

UnityでAndroid実機ビルド時の話。
そんな時にチェックする5つのポイントです。

1.端末とPCは本当につながっているか?

当たり前ですが、PCと実機がUSBケーブルでつながっていますでしょうか?
そして、USBケーブルは故障していませんか?

2.開発者向けオプションがONになっているか?

Android実機の開発者向けオプションは有効化しておく必要があります。

開発者向けオプションの出し方は開発者向けオプション 出し方とかで検索するとたくさん出てきます。

3.USBデバッグがONになっているか?

開発者向けオプションの設定の中のUSBデバッグが有効化しておく必要があります。

4.USBデバッグをAndroid端末側で許可しているか?

上図のようにUSBデバッグを許可する必要があります。

PCにUSBで接続した時、またUSBデバッグを有効化したタイミングでダイアログが表示されます。 
OKを押しましょう。

5.Developmentビルドになっているか?

Unity側のビルド設定です。

Development Buildを有効化しておく必要があります。

最後に

以上よく忘れるやつの共有でした。

以前ブログの方で似たような事を書いていましたので、そちらも共有。
AndroidがUnityProfilerにつながらない時にチェックした5つの事

環境

  • Unity2019.4
  • Android 11
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Flutter で仕事したい人のための Widget 入門

この記事について

Flutter の入門記事の中には、初学者にもわかりやすく説明しようとするあまり実際のアプリ開発で重要になる内容まで抜け落ちてしまったり、意訳や曲解によって誤解を与えてしまう内容が含まれることもあったりします。

この記事は、Flutter で本格的にアプリ開発を進める上で僕が先に知りたかったこと、詳しい説明がほしかった部分を、過去の自分と同じような Flutter 初学者向けに説明する記事です。

本来は公式ドキュメントを読みながら学習を進められるのが理想ではありますが、Flutter のドキュメントは全て英語であること、またある程度のアプリ開発経験があることを想定した難易度になっている(ように感じる)ことから、まずは日本語の記事を頼りに学習を進める方も多いと思います。この記事は、そんな方の役に立つ内容を目指しています。

この記事では、中でも Flutter の Widget に焦点を当てて書いています。

本文

Everything is a Widget

Flutter には "Everything is a Widget" というスローガンがあります。

https://flutter.dev/docs/resources/inside-flutter#conclusion

これは、UI を構築する全ての要素を Widget として表現する(つまり、 Widget クラスのサブクラスとして定義する)ことを目指した設計で、 Flutter 最大の特徴の1つに上げられることも多い考え方です。

例えば、画面にテキストを表示する Text は Widget のサブクラスですし、その周りのパディングを設定する Padding もやはり Widget です。ほかにも、「この時は Text を出す、この時は出さない」という設定は Visibility で実現でき、これもやはり Widget です。

宣言的プログラミング

そんな Widget を使って実際に画面を作るソースコードは以下のような書き方をします。

return Visibility(
  visible: _isVisible,
  child: const Padding(
    padding: const EdgeInsets.all(16),
    child: Text('Hello, Flutter!'),
  ),
);

これは、 _isVisible 変数が true であれば、四方を 16 のパディングで囲まれた Text で 'Hello, Flutter' を画面に表示する 、という内容です。

もし Kotlin や Swift でのアプリ開発経験がある方であれば、画面を作るための考え方が全く違うことに気が付くのではないかと思います。

おそらく同じようなことを Kotlin でしたければ、以下のようなコードになるでしょう。

if (isVisible) {
    val text = TextView(context);
    text.text = "Hello, Flutter!";
    text.setPadding(16, 16, 16, 16);

    view.addView(text);
}

(ちょっと Kotlin を忘れ気味なので) 細かなコードの正しさや内容は置いておいて、プログラムの作りが全く異なることがひと目で分かるのではないでしょうか。

Kotlin のコードは、

  • もし isVisibletrue であれば
  • TextView インスタンスを生成して
  • text フィールドに文字列を代入して
  • setPadding() でパディングを設定して
  • 親の View に追加する

という、手順を踏まえて1行1行処理を進めていく形で UI を構築します。とても「プログラミング的」と言えると思います。

一方で Flutter は、

  • Visibility を配置する
  • 表示 / 非表示の判定には _isVisible の値を使用する
  • Visibility の childPadding である
  • Padding の値は padding プロパティにセットした通りである
  • Padding の childText である
  • Text が表示する文字列は "Hello, Flutter!" である

と、まるで設定ファイルを記述するようにコーディングします。少なくとも、1行1行でメソッドを呼び出したりフィールドに値をセットしたり、といった作業ではないことは明らかです。

このようなコーディングスタイルを、「宣言的」(declarative) なプログラミングと呼びます。1行ずつ処理を呼び出したり条件分岐するのではなく、「これを使う」「この値はこうである」という「宣言」であるように読めることからこのような名称がつけられています。1

注意したいのは、これも立派なプログラムである、ということです。

return Visibility();

は Visibility クラスのインスタンスを生成して返却するコードで、そこに引数(プロパティ)の visiblechild を渡すコードを追加すると

return Visibility(visible: _isVisible, child: Padding(),);

となり、ここに改行を入れると

return Visibility(
  visible: _isVisible,
  child: Padding(),
);

となります。さらに、Padding も同様に paddingchild といったプロパティを渡して改行を入れていくと、先ほどのコードのように

return Visibility(
  visible: _isVisible,
  child: const Padding(
    padding: const EdgeInsets.all(16),
    child: Text('Hello, Flutter!'),
  ),
);

というような「宣言的」なコードが完成する、というわけです。

言い換えると、 return するためのインスタンスを生成して、コンストラクタに渡す値もその場でインスタンス生成したもので、さらにそのコンストラクタに渡す値もその場でインスタンス生成したもので、ということを繰り返すと上記のようなコードになりますが、あくまでプログラムです。そのため、以下のような書き方も一応可能です。

final text = Text('Hello, Flutter!');

final padding = const Padding(
  padding: const EdgeInsets.all(16),
  child: text,
),

return Visibility(
  visible: _isVisible,
  child: padding,
);

しかし、 child の中身がコード的に上の行に遡らなければ読めなくなってしまったり、どの Widget とどの Widget がどう関連しているのかがぱっと見で読みづらくなってしまう問題があることから、基本的には最初のコードのようにネストを深くしていく書き方をするのが主流です。2

1画面分のプログラム

では、以上を踏まえて画面を1つ作ってみたいと思います。

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

void main() => runApp(LoginPage());

class LoginPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('ログイン画面'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(32),
          child: Column(
            mainAxisSize: MainAxisSize.max,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              TextFormField(
                decoration: InputDecoration(
                  hintText: 'chooyan@example.com',
                ),
              ),
              const SizedBox(height: 32),
              ElevatedButton(
                onPressed: () {},
                child: Text('ログインメールを送る'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

このプログラムを実行すると、↓ のような画面が表示されます。

image.png

ソースコードの全てを読み解く必要はありませんが、先述した通り、ネストが深くなっていること、「命令的」ではなく「宣言的」にコーディングされていること、またマテリアルな UI を構築するための MaterialApp やパディングを設定する Padding、縦に Widget を並べる Column といったものが全て Widget で表されてることに注目してみてください。

Widget を構築する Widget

先ほどのログインページを構築するためのコードは build() メソッドに書いていましたが、この build() メソッドを持つクラスもまた Widget です。

class LoginPage extends StatelessWidget {

Widget ですので、このクラス自体のインスタンスを先ほど出てきた Paddingchild プロパティに渡すようなこともできますし、 AppBartitle のような、通常 Text を渡すような場所にも渡すことができます。

LoginPage のような StatelessWidget (または StatefulWidget) のサブクラスが Text や Column などと違うのは、それが 「Widget を構築する Widget である」 という点です。公式ドキュメントにも次のように書かれています。

A stateless widget is a widget that describes part of the user interface by building a constellation of other widgets that describe the user interface more concretely.
訳: StatelessWidget は他のより具体的な Widget を集めて UI の一部を表現する Widget です。

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

「他のより具体的な Widget」とは、 Padding や Container といった子 Widget の配置方法を決める Widget や、 Text や Image のような画面に何かを描画するための Widget を指します。3 4 これらの Widget は RenderObjectWidget という、描画のための RenderObject を生成する役割を持ったクラスで、複数の Widget を組み立てる build() のようなメソッドは持っていません。

一方で StatelessWidget や StatefulWidget は、具体的な何かを描画しない代わりに、 build() メソッドを実装することで「どの Widget をどのように配置するか」を指定する役割を持っています。そしてそれは Text や Image と違ってアプリごと、画面ごとに様々ですので、われわれアプリ開発者はこの 「Widget を構築する Widget」 をコーディングしていく のが Flutter でアプリ開発する上での主な仕事のひとつになる、というわけです。

もちろん、ひとつの StatelessWidget / StatefulWidget にどれだけのまとまりで Widget を集めるかは設計次第です。 1, 2個の Widget を汎用的に使えるよう配置して再利用するものもあれば、 LoginPage のように 1 ページ丸々記述する場合もあります。

どれだけ長くなったらどのように分割するかは実際にコーディングしながら試行錯誤すると良いでしょう。その点では通常のコーディングでメソッドの分割具合を考える感覚と変わりありません。正解もありません。

まとめ

Flutter は "Everything is a Widget" のスローガンの通り、アプリ開発者は Widget の使い方さえ知っていればアプリの UI が作れてしまうように API がデザインされています。

しかし、その「Widget の使い方」を知るためには、まず「宣言的」に書くことや、 Widget を自作すること、 Widget にもその役割によって種類があること、などを押さえておくことが、 Flutter を「雰囲気で」書くのをやめるためには重要です。

それを理解するための第一歩として、この記事に書いたことが役に立てれば嬉しいです。

なお、 Flutter は 標準搭載されている Widget の種類がとても豊富 なことも特徴のひとつです。もしかしたら、何かの共通パーツとして Text や Container などを駆使して Widget を自作したら、もうすでに同じような(場合によってはよりクオリティの高い) Widget が公式から提供されている、ということも珍しくありません。

すでに用意されている Widget を効率的に使ってより良いアプリを楽に作れるようになることを目指して、 Widget of the WeekWidget Catalogue などを日常的に眺める習慣をつけてみるのも良いでしょう。5


宣伝

"Everything is a Widget" とは言うものの、それはアプリ開発者向けの外向きな方針であって、実際の仕組みはもう少し複雑です。Widget の他にも ElementRenderObject というものがあり、それぞれの Element が親子の参照を保持してツリー構造を形成することで Flutter の仕組みの大部分が実現されていたりと、中身に目を向ければさらに Flutter に詳しくなり、開発や学習の効率も向上します。

そのあたりに興味のある方はぜひ 「【Flutter】Navigator.of(context) から理解する 3つのツリー」 も読んでみてください。少し Flutter に慣れた方向けの記事ですが、じっくり読めば(参照先の公式サイトなども含めて)さらにたくさんの知見を得られるはずです。


  1. ちなみに Kotlin のような、1つ1つ処理を呼び出すスタイルは「命令的」(imperative) と呼ばれています。 

  2. 当然、限度はあります。あまりにネストが深くなる場合は、適宜メソッドや別クラスに切り出したりして読みやすさを調整します。 

  3. 厳密には、 Text は StatelessWidget、 Image は StatefulWidget で、これらの Widget も同じく「他のより具体的な Widget を集めて UI の一部を表現する Widget」です。 Text は build() の中で RichText という「具体的な Widget」を、 Image は同じく build() の中で RawImage という「具体的な Widget」をそれぞれ生成して返却しています。 

  4. Text について、詳しく知りたい場合は 【Flutter】Text とは何か も読んでみてください。 

  5. 僕も Zenn の方に 1 つひとつの Widget の仕組みに着目した記事をいくつか書いていますので、何かの参考に読んでみていただければ嬉しいです。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

collbackFlowをFirebase Realtime Databaseに使うといい感じ

概要

個人AndroidアプリでFirebase Realtime Database(略してRTDB)を使っているが、ValueEventListenerのコールバックで実装する書き方がイマイチ好きではなかった。

そこでcollbackFlow を使うと、RTDBとの接続をキレイに分離可能かつがStateFlow化できて、スッキリ書くことができた。
また、RTDBとの相性もよくて、値が何度も更新されてもFlowでちゃんと受け取れる。

コード: 状態管理

例として、天気を取得する機能を実装するコードを紹介する。

StateFlowで管理する状態クラス
今回は分かりやすいようにSuccess内の変数をWeatherInfoにしている

sealed class WeatherUiState {
    // 値の取得成功
    data class Success(val weatherInfo: WeatherInfo) : WeatherUiState()
    // 失敗
    data class Error(val message: String) : WeatherUiState()
}

コード: Repository

RTDBに接続して値を取得してくる

class WeatherRepository(private val dbRef: DatabaseReference) {

    /**
     * 天気を取得
     */
    @ExperimentalCoroutinesApi
    fun fetchWeather(): Flow<WeatherUiState> = callbackFlow { // callbackFlowの戻り値はFlow型
        // DBへの接続
        dbRef.addValueEventListener(object : ValueEventListener {
            // 正常に取得できた場合
            override fun onDataChange(snapshot: DataSnapshot) {
                snapshot.getValue(WeatherInfo::class.java)?.let { weather ->
                    // offerで購読側に値を流せる
                    offer(WeatherUiState.Success(weather))
                }
            }

            // エラーの場合
            override fun onCancelled(error: DatabaseError) {
                offer(WeatherUiState.Error(error.message))
            }
        })
        awaitClose {
            // 後始末はここでするが、DatabaseReferenceは自動で破棄されるで、やる事なし
        }
    }

コード: ViewModel

RepositoryのStateFlowを購読するだけ。

class WeatherViewModel : ViewModel() {

    private val _weather = MutableLiveData<WeatherInfo>()
    val weather :LiveData<WeatherInfo> = _weather

    @ExperimentalCoroutinesApi
    fun fetchWeather() {
        viewModelScope.launch {
            // StateFlowの購読
            weatherRepository.fetchWeather().collect { state ->
                when (state) {
                    // 成功
                    is WeatherUiState.Success ->  _weather.value = state.weatherInfo
                    // エラー処理
                    is WeatherUiState.Error -> // 画面にエラー通知など
                }
            }
        }
    }

感想

Flowの学習もかねてcallbackFlowを触ったが、すごくシンプルかつスッキリ書けていいね。

今回はFirebase RTDBと組み合わせたが、Firestoreにも同じように使えるはず。

あと、位置情報やBLEスキャンにcallbackFlowを使っている記事もあるので、けっこう便利に使えそう

参考リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】 DB上に保存しているレコードをCSV形式(カンマ区切り)で保存する。

ご挨拶

かなり久々の投稿になります。少し雑談から。急ぎの方は横の目次からジャンプしてください。
2020年は今まで生きてきた中である意味衝撃的でエキセントリックな1年でした。
皆さんも環境の変化やコロナへの不安などで、心が落ち着かない毎日を過ごされたかと思います。

自分も働く環境など、いろんなものが今までからガラッと180度変わった1年でした。
まさか、BTSにハマるなんて思いもしなかったし。(この投稿を書きながら今も聞いています。)

そんな中でもこの投稿を(ひいてはQiitaを)見に来られた方は、何かしらのシステム(アプリ)を自分の手で作り出すことに夢中になっている方かとお見受けします。

自分が嫌になることが多いこの職業ですが、「自分はできる!」と信じて頑張って欲しいです。

自分が投稿しているものは深い技術のものではなく、素人程度の技術をシンプルに応用しているものが
多いです。
まだまだ勉強中の身ですが、アプリ開発をされている方の少しでもお役に立てれば幸いです。

今回の目的

DB上に保存しているレコードをCSV形式(カンマ区切り)で保存して、そのデータを取り込むまでの
一連の流れを実装したい。

色々方法を模索したが、SAF(StorageAccessFramework)を今回採用した。
というよりも、これからのバージョンはこの方法が主流になるらしい。(Android4.4以降)

→ Open files using storage access framework -developer.android-

使用する環境

  • AndroidStudio Ver 3.4.1
  • Android 10.0 (Q) API 29

Permisson(アクセス権限)

注意しないといけないのは、Android6.X(API 23)以降からPermission(アクセス権限許可)をユーザに通知しないといけないこと。
今回はストレージにアクセスするので、ストレージへのアクセス権限をユーザに許可してもらわないといけません。
ストレージにアクセス出来ないというのは致命的なので、この機能を実装する前にその部分を実装してください。
詳細は以下のサイトを参考にしてください。(時間が空けばこの部分も記事にします。)

権限の概要-developer.android-

エクスポート編

では「エクスポート」から。
下記は別アプリケーションで保存先を指定するためのクラス。

MainActivity.java
public void fileopen_for_SAF() {
  try {
        //ファイル名を作成。
        StringBuilder fileName = new StringBuilder();
        fileName.append(/* 好きな名前 */);
        fileName.append("_");
        fileName.append(getToday()); //ここで作成日時を取得。
        fileName.append(".csv");

        //「Intent.ACTION_CREATE_DOCUMENT」は、ファイルを選択するためのIntent。
        Intent it = new Intent(Intent.ACTION_CREATE_DOCUMENT);

        //ここで取得するファイルの種類を制限する。(今回はALLタイプ)
        it.setType("*/*");

        //ファイル名をセットして、Intentを起動。
        it.putExtra(Intent.EXTRA_TITLE, fileName.toString());

        /* 「CREATE_DOCUMENT_REQUEST」は、Private intで予め定義しておく。 */
        startActivityForResult(it, CREATE_DOCUMENT_REQUEST);

      } catch (Exception e) {
          e.printStackTrace();
      }

作成日付取得クラス。(Calendarクラスをインポートする必要あり。)
作成結果は「(好きな名前)_2021_01_0500_15.csv」みたいに作成されます。

MainActivity.java
private String getToday() {
   try {
       StringBuilder today = new StringBuilder();
       today.setLength(0);

       //日付の設定
       dd = Calendar.getInstance();
       today.append(dd.get(Calendar.YEAR));
       today.append("_");
       today.append(dd.get(Calendar.MONTH) + 1);
       today.append("_");
       today.append(dd.get(Calendar.DAY_OF_MONTH));
       today.append("_");
       today.append(dd.get(Calendar.HOUR));
       today.append("_");
       today.append(dd.get(Calendar.MINUTE));

       return today.toString();
   } catch (Exception e) {
       e.printStackTrace();
   }
   return null;
}

インテントの結果は、「onActivityResult」に返ってくる。
他にインテントの結果が返ってくるようなものがあった場合のことを考えて、requestCodeは、エクスポート用に変数を分けておく。

MainActivity.java
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {

    /* 「CREATE_DOCUMENT_REQUEST」は、Privateで予め定義しておく。 */

    if (requestCode == CREATE_DOCUMENT_REQUEST) {
         //エクスポート
         if (resultCode == RESULT_OK) {
             Uri create_file = data.getData();  //取得した保存先のパスなど。

             //出力処理を実行。その際の引数に上記のUri変数をセットする。
             if (exportCsv_for_SAF(create_file)) {
                 //出力に成功した時の処理。
             } else {
                 //出力に失敗した時の処理。
             }
         } else if (resultCode == RESULT_FAILED) {
             //そもそもアクセスに失敗したなど、保存処理の前に失敗した時の処理。
         }
     }

     //リストの再読み込み
     this.LoadData();

     super.onActivityResult(requestCode, resultCode, data);
 }

実際に出力を行うクラス。
DB部分はご自分の環境に合わせて作成してください。

MainActivity.java
public Boolean exportCsv_for_SAF(Uri openFile) {

    try {
        OutputStream os = getContentResolver().openOutputStream(openFile);
        OutputStreamWriter os_write = new OutputStreamWriter(os, Charset.forName("Shift_JIS")); //後々インポートする際に困るのでエンコードを指定しておく。
        PrintWriter pw = new PrintWriter(os_write);

        StringBuilder rec = new StringBuilder(); //Export_data用

        //Read Data

        /* ここにExportするデータを取得するためのDBインスタンスなどを記述する。*/

        StringBuilder sql = new StringBuilder();
        sql.setLength(0);
        sql.append(/* 取得用のSQL */)

        c = db.rawQuery(sql.toString(), null);
        while (c.moveToNext()) {
            rec.setLength(0);

            rec.append(/* 取得カラムデータをExport用Builderにセット。 */);
           /* ↑をExportするカラム分呼び出す。*/

            pw.println(rec.toString()); //Export

        }

        //基本的に開けたら閉める。
        c.close();
        pw.close();
        os_write.close();
        os.close();

    } catch (SQLiteException e) {
        e.printStackTrace();
        return false;
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        return false;
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    } finally {
        if (db.isOpen()) {
            db.endTransaction();
        }
    }
    return true;
}

とりあえずここまで。

インポート編

  • 制作中

終わりに

ここまでお疲れ様でした。というより疲れた。
データのインポート/エクスポートをするという目的なだけで色々調べる事になったので、勉強になりました。
一番シンプルに実装するとこの記事のようにできますので、ご自分で工夫してぜひお試しください。
他にも「アプリケーションが独自のストレージを提供すること」などができるみたいなので、時間がある時に試してみます。

P.S.
インポート編は時間がある時に追記します。

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む