- 投稿日:2021-01-05T23:25:20+09:00
【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を入れるだけでスキーマを作ってくれます!楽チンですねまとめ
この記事で少しでも開発が捗ったら嬉しいです!
- 投稿日:2021-01-05T22:54:03+09:00
InheritedWidgetの目的と使い方【Flutter】
まえがき
InheritedWidget
の使い方の基本をまとめます!Flutterを勉強していてよくわからなくなるポイントの一つがこの
InheritedWidget
だと思います。筆者自身、これを理解するのにかなり時間がかかったので、「こういう説明があったらよかったな」と思う説明を書きます。
なお、以下では、
InheritedWidget
クラスを継承したクラスのことを単にInheritedWidget
と呼びますね。他のStatefulWidget
なども同様です。前提知識
FlutterアプリがWidgetのツリー構造でできていることを知っていて、
StatefulWidget
とsetState
は使えるものとします。
BuildContext
の知識も少しだけ要りますが、「Widgetツリー内での位置を表現するもの」程度の理解で十分です。それについてはこちらをお読みください。
記事内の例の実行方法
該当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)
です。この
n
はBuildContext
から実際に返るState
までの、Widgetツリーの深さを表します。つまりWidgetツリーが深くなると、それに比例して計算に時間がかかるようになり、アプリが重くなっていくのです。使用例
InheritedWidget
は、StatelessWidget
やStatefulWidget
と同様に、InheritedWidget
クラスをextends
したクラスを自分で作って使います。この例では
MessageData
というInheritedWidget
を作っています。
updateShouldNotify
については後ほど説明しますので今はスルーしてください。下記の
MainWidget()
を、MaterialApp
で覆ってrunApp
に渡します。just_access.dartimport '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
が取得され、表示されます。Widgetツリー内で
InheritedWidget
を上書きするWidgetツリー内の一部でだけ異なる
MessageData
を使いたい時は、もう一度MessageData
Widgetを配置すればいいです。Theme, MediaQuery, Directionalityなど多くのFlutter Widgetがこの性質を持っていますね。実はそこでも
InheritedWidget
が利用されています。次の例を見てください。
MessageData
は2箇所配置されていますが、より呼び出し元のBuildContext
に近い方からmessage
が取得されていることを確認してください。
MessageData
のプロパティmessage
はコンストラクタで外から受け取れるようにしました。override_message.dartimport '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); } }プロパティが変化した時に下層の
BuildContext
に更新を伝えてリビルドさせる
InheritedWidget
のインスタンスは、中身を変更できません。にもかかわらず更新を伝播するとは、一体どういうことなのでしょう?変更不可能なのに更新するとは??
筆者は以前、この部分で混乱していました。
答えはこうです。
InheritedWidget
のインスタンスを何度も作り直して置き換える状態を更新可能な
StatefulWidget
などと組み合わせて、Widgetツリー内にあるInheritedWidget
のインスタンスを作り直して再配置します。プロパティの異なる
InheritedWidget
を作って置き直せば、プロパティの変更が、それに依存するBuildContext
に伝播され、リビルドされるわけです。使用例
実際の例で見てみましょう。
StatefulWidget
を使って、1秒に1回、プロパティを1ずつ増やしたInheritedWidget
を作って再配置します。作るInheritedWidget
の名前はCountData
としています。
InheritedWidget
のchild
に渡すWidgetは、変更せず、同じインスタンスを毎回渡すことにします。update_count.dartimport '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'); } }
updateShouldNotify
で更新を間引きする
InheritedWidget
のよくわからないポイントの一つ、updateShouldNotify
です。ここまでの説明で、
InheritedWidget
のインスタンスの置き換えが行われる際に更新が伝播されるようなことを言いましたが、実は、実際に更新が伝播されるのは
updateShouldNotify
がtrue
を返した時だけです。インスタンスの置き換えの際、古い方のインスタンスも手元に手に入るので、比較して、更新通知するか制御可能です。
例えば先程の例で、更新を2回に1回にする(つまり、
count
を2で割った商が変化した時だけ更新する)場合、updateShouldNotify
は次のように書けます。@override bool updateShouldNotify(CountData old) { return count ~/ 2 != old.count ~/ 2; }他は先程の例と全く同じようにすると、次のような動きになります。
updateShouldNotify
の中身を、場合によって都合のいいように実装すればいいわけですね!慣例として、
of
を実装するここまで、動きの理解を重視して、BuildContext.dependOnInheritedWidgetOfExactTypeを直接呼んでいましたが、
Flutterの慣例として、このメソッドは直接呼ばず、
InheritedWidget
にof
というstaticメソッドを実装して、そこから呼ぶのが普通です。例
最初の
MessageData
の例ですと、このようになります。implement_of.dartimport '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
を、アプリの状態管理にガッツリ使うことを考えます。先程のタイマーを使った例では、更新が
StatefulWidget
のState
内部から自発的に発生していました。一方実際のアプリでは、Widgetツリー下層にある
RaisedButton
が押されたら、上層で保持している状態を更新する、という動きがよくあります。となると、Widgetツリーの下層から上層の状態を更新するメソッドを呼ぶ必要があるわけです。
下層から更新用メソッドにアクセスできるようにする方法は、例えば
- その更新用メソッドを、毎回Widgetのコンストラクタに渡して下層まで持っていく
という方法がありますが、これは中間層すべてのコンストラクタに記述が必要で、イマイチです。
せっかく
InheritedWidget
があるので、更新用メソッドにも下層からO(1)
でアクセスしましょう!今回は、公式のList of state management approachesからリンクされている
これらを参考に、一つ方法を紹介します。
InheritedWidget
でState
自体を下層に流せ!
StatefulWidget
を使う場合、プロパティも更新用メソッドもState
が持っていることが多いです。更新時にsetState
を呼ばないといけないので、更新用メソッドもState
が持たないといけない。ならば、
State
自体をInheritedWidget
で下層に流してしまえば、そのプロパティにアクセスするのも更新用メソッドにアクセスするのも簡単ですね!その方法を簡単にまとめるとこうです。
StatefulWidget
とInheritedWidget
のペアで使うInheritedWidget
はprivate
にするState
をpublic
にするInheritedWidget
はdata
というプロパティを持ち、State
を格納するof
メソッドはStatefulWidget
が持ち、State
(InheritedWidget
のdata
プロパティ)を返すStatefulWidget
はchild
を受け取り、State
のbuild
でInheritedWidget
の直下に入れて返す- メソッドだけ欲しい場合は
BuildContext.getElementForInheritedWidgetOfExactType
を使うと、更新伝播の対象にならない実例でコメントと一緒に見るのが早いと思いますので、次をご覧ください。
使用例
ボタンが押された回数をテキストで表示するだけのアプリを考えます。
ただし、状態管理WidgetはWidgetツリーの上層にあり、ボタンとテキストはツリーの違う枝に置きます。
今回は状態更新用WidgetとアプリUI用Widgetでファイルを分けます。
count
を管理するStatefulWidget
+InheritedWidget
のセットまず、状態管理用のWidgetを用意します。
count_manager.dartimport '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, ); } }
BuildContext
のgetElementForInheritedWidgetOfExactTypeは、InheritedWidget
の中身にアクセスしつつも、BuildContext
を状態更新時のリビルド対象として登録しません。状態更新用メソッドだけを取得して、リビルド対象にしたくない場合、こちらを使います。
(
BuildContext
のgetElementForInheritedWidgetOfExactTypeの返り値はInheritedWidget
ではなくInheritedElementなので、返り値の処理が若干変わってます)UI構築
update_method.dartimport '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'); } }Providerパッケージについて
先程の例の
count_manager.dart
は50行近くあるのですが、count
プロパティとincrement
メソッド以外は、毎回ほぼ同じ内容を書くことになります。そこで、それをパッケージ化すると便利そうだなと思うわけなのですが、
そうして出来上がったパッケージがProviderパッケージです。
(実際には、他にもいろいろな機能が追加されているので単純にそうとは言えませんが)
ということで、基本的には
Provider
パッケージを使えばいいと思います。同じ作者によってさらなる改良がなされたRiverpodもありますね。
もちろん、他にもお好みの状態管理手法を使ってもいいです。
この記事で紹介した方法を使ってもいいと思います。
この記事で紹介した方法は、Flutter以外のパッケージに一切依存しないところがメリットですね。
おわり
おわりです!これで
InheritedWidget
を理解してもらえたら嬉しいです!
- 投稿日:2021-01-05T14:39:09+09:00
文字列(リソース名)で画像を取得してImageViewに貼り付ける(kotiln)
以前書いた記事
画像名を文字列で指定してImageViewに貼り付けるコード(android)これにLGTMしていただいていたので見てみたのですが、微妙だったので書き直します(ついでにkotlinで)
リソース名からImageViewに画像をセットするメソッドは、swiftにはあるんですがkotlinだとありそうでないです。ちなみにswiftでは
imageView.image = UIImage(named:"hoge.png")こんな感じでimageViewに画像をセットできます。
同じようなものをkotlinで再現してみます。
resourceIdを文字列から取得
StringExtensions.ktfun String.getResourceId(context: Context): Int { return context.resources.getIdentifier(this, "drawable", context.packageName) }Stringの拡張クラスを定義しました
画像をImageViewにセット
ImageViewExtensions.ktfun 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との比較
swiftimageView.image = UIImage(named:"some_image_name_string.png")kotlinimageView.setImageResourceByName("some_image_name_string")大体似たような感じになったかと思います。
上記で定義したStringとImageViewの二つの拡張をコピペすればそのまま使えます。
- 投稿日:2021-01-05T13:49:48+09:00
Android アプリ開発時の参考資料集
概要
とあることから、Google Maps APIを使用したAndroidアプリを作成することになったので、
参考にしたリンクを備忘録のために纏めました。環境編
前提
Androidコードディング規約
もしあなたが急にAndroidアプリを業務で作るはめになった場合の選択肢(2021年初頭版)Android Studio
Android studio の Gradleについて調べてみた
Android Studioでの外部ライブラリの読み込み方と注意点ドキュメント
Javadocの作成方法を現役エンジニアが解説【初心者向け】
設計編
コーディング編
パーミッション
Androidパーミション別-何してんの?#5 NETWORK編
設定画面のインテントアクションまとめ画面遷移
[Android] 10分で作る、Navigationによる画面遷移
最初のActivityに戻る
Fragmentによる画面遷移でハマった
Fragmentで、パラメータ付きの画面遷移を実装するView要素
Button
[Android] Button のonClickListenerの設定が色々できる件
Fragment
Fragment自身を終了させる
AndroidStudioの標準デザインパターンで分かるFragment入門
AndroidでFragmentの結果をActivityで受け取るHTTP通信
GPS関連
Callback・マルチスレッド・タスク
Handler
Handler と Looper
Handlerの使い方を紹介する
AndroidでTimerを使わずHandlerだけでお手軽に定期実行してあげる
Handlerクラスの正しい使い方(Androidでスレッド間通信)
幸せな非同期処理ライフを満喫するための基礎から応用まで
【Java】Handlerクラスについてまとめてみました
【Android】HanlderとMessageを使ってマルチスレッドの処理結果を受け取るTask
スレッドセーフ
ライフサイクル
onCreate と onStart と onResume の違い
知らずに作って大丈夫?Androidの基本的なライフサイクルイベント31選
Androidの画面設計や遷移に関して整理してみるその他
プログレスバー
Androidでプログレスダイアログを使用して、処理を行う
【Android】ProgressDialogがDeprecatedになっている理由、正しく理解してます?Maps API編
GoogleMapsAPI
Google Maps Platform ドキュメント
地図オブジェクト
AndroidのアプリにGoogle Mapを組み込む
Android - Polyline encoding algorithm?まとめ
とりあえず、リンクを羅列しましたが、各リンクから何が得られるのか、余裕があり、気力が沸いたら書こうとおもいます。
- 投稿日:2021-01-05T13:49:48+09:00
Android アプリ開発した際の躓きポイントを纏めてみた
概要
とあることから、Google Maps APIを使用したAndroidアプリを作成することになったので、
参考にしたリンクを備忘録のために纏めました。環境編
前提
Androidコードディング規約
もしあなたが急にAndroidアプリを業務で作るはめになった場合の選択肢(2021年初頭版)Android Studio
Android studio の Gradleについて調べてみた
Android Studioでの外部ライブラリの読み込み方と注意点ドキュメント
Javadocの作成方法を現役エンジニアが解説【初心者向け】
設計編
コーディング編
パーミッション
Androidパーミション別-何してんの?#5 NETWORK編
設定画面のインテントアクションまとめ画面遷移
[Android] 10分で作る、Navigationによる画面遷移
最初のActivityに戻る
Fragmentによる画面遷移でハマった
Fragmentで、パラメータ付きの画面遷移を実装するView要素
Button
[Android] Button のonClickListenerの設定が色々できる件
Fragment
Fragment自身を終了させる
AndroidStudioの標準デザインパターンで分かるFragment入門
AndroidでFragmentの結果をActivityで受け取るHTTP通信
GPS関連
Callback・マルチスレッド・タスク
Handler
Handler と Looper
Handlerの使い方を紹介する
AndroidでTimerを使わずHandlerだけでお手軽に定期実行してあげる
Handlerクラスの正しい使い方(Androidでスレッド間通信)
幸せな非同期処理ライフを満喫するための基礎から応用まで
【Java】Handlerクラスについてまとめてみました
【Android】HanlderとMessageを使ってマルチスレッドの処理結果を受け取るTask
スレッドセーフ
ライフサイクル
onCreate と onStart と onResume の違い
知らずに作って大丈夫?Androidの基本的なライフサイクルイベント31選
Androidの画面設計や遷移に関して整理してみるその他
プログレスバー
Androidでプログレスダイアログを使用して、処理を行う
【Android】ProgressDialogがDeprecatedになっている理由、正しく理解してます?Maps API編
GoogleMapsAPI
Google Maps Platform ドキュメント
地図オブジェクト
AndroidのアプリにGoogle Mapを組み込む
Android - Polyline encoding algorithm?まとめ
とりあえず、リンクを羅列しましたが、各リンクから何が得られるのか、余裕があり、気力が沸いたら書こうとおもいます。
- 投稿日:2021-01-05T13:01:37+09:00
【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
- 投稿日:2021-01-05T01:50:41+09:00
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 のコードは、
- もし
isVisible
がtrue
であればTextView
インスタンスを生成してtext
フィールドに文字列を代入してsetPadding()
でパディングを設定して- 親の View に追加する
という、手順を踏まえて1行1行処理を進めていく形で UI を構築します。とても「プログラミング的」と言えると思います。
一方で Flutter は、
Visibility
を配置する- 表示 / 非表示の判定には
_isVisible
の値を使用する- Visibility の
child
はPadding
である- Padding の値は
padding
プロパティにセットした通りである- Padding の
child
はText
であるText
が表示する文字列は "Hello, Flutter!" であると、まるで設定ファイルを記述するようにコーディングします。少なくとも、1行1行でメソッドを呼び出したりフィールドに値をセットしたり、といった作業ではないことは明らかです。
このようなコーディングスタイルを、「宣言的」(declarative) なプログラミングと呼びます。1行ずつ処理を呼び出したり条件分岐するのではなく、「これを使う」「この値はこうである」という「宣言」であるように読めることからこのような名称がつけられています。1
注意したいのは、これも立派なプログラムである、ということです。
return Visibility();は Visibility クラスのインスタンスを生成して返却するコードで、そこに引数(プロパティ)の
visible
とchild
を渡すコードを追加するとreturn Visibility(visible: _isVisible, child: Padding(),);となり、ここに改行を入れると
return Visibility( visible: _isVisible, child: Padding(), );となります。さらに、Padding も同様に
padding
やchild
といったプロパティを渡して改行を入れていくと、先ほどのコードのように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 がどう関連しているのかがぱっと見で読みづらくなってしまう問題があることから、基本的には最初のコードのようにネストを深くしていく書き方をするのが主流です。21画面分のプログラム
では、以上を踏まえて画面を1つ作ってみたいと思います。
main.dartimport '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('ログインメールを送る'), ), ], ), ), ), ); } }このプログラムを実行すると、↓ のような画面が表示されます。
ソースコードの全てを読み解く必要はありませんが、先述した通り、ネストが深くなっていること、「命令的」ではなく「宣言的」にコーディングされていること、またマテリアルな UI を構築するための
MaterialApp
やパディングを設定するPadding
、縦に Widget を並べるColumn
といったものが全て Widget で表されてることに注目してみてください。Widget を構築する Widget
先ほどのログインページを構築するためのコードは
build()
メソッドに書いていましたが、このbuild()
メソッドを持つクラスもまた Widget です。class LoginPage extends StatelessWidget {Widget ですので、このクラス自体のインスタンスを先ほど出てきた
Padding
のchild
プロパティに渡すようなこともできますし、AppBar
のtitle
のような、通常 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 Week や Widget Catalogue などを日常的に眺める習慣をつけてみるのも良いでしょう。5
宣伝
"Everything is a Widget" とは言うものの、それはアプリ開発者向けの外向きな方針であって、実際の仕組みはもう少し複雑です。Widget の他にも Element や RenderObject というものがあり、それぞれの Element が親子の参照を保持してツリー構造を形成することで Flutter の仕組みの大部分が実現されていたりと、中身に目を向ければさらに Flutter に詳しくなり、開発や学習の効率も向上します。
そのあたりに興味のある方はぜひ 「【Flutter】Navigator.of(context) から理解する 3つのツリー」 も読んでみてください。少し Flutter に慣れた方向けの記事ですが、じっくり読めば(参照先の公式サイトなども含めて)さらにたくさんの知見を得られるはずです。
ちなみに Kotlin のような、1つ1つ処理を呼び出すスタイルは「命令的」(imperative) と呼ばれています。 ↩
当然、限度はあります。あまりにネストが深くなる場合は、適宜メソッドや別クラスに切り出したりして読みやすさを調整します。 ↩
厳密には、 Text は StatelessWidget、 Image は StatefulWidget で、これらの Widget も同じく「他のより具体的な Widget を集めて UI の一部を表現する Widget」です。 Text は
build()
の中でRichText
という「具体的な Widget」を、 Image は同じくbuild()
の中でRawImage
という「具体的な Widget」をそれぞれ生成して返却しています。 ↩Text について、詳しく知りたい場合は 【Flutter】Text とは何か も読んでみてください。 ↩
僕も Zenn の方に 1 つひとつの Widget の仕組みに着目した記事をいくつか書いていますので、何かの参考に読んでみていただければ嬉しいです。 ↩
- 投稿日:2021-01-05T01:15:55+09:00
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を使っている記事もあるので、けっこう便利に使えそう
参考リンク
- 投稿日:2021-01-05T00:17:36+09:00
【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(アクセス権限許可)をユーザに通知しないといけないこと。
今回はストレージにアクセスするので、ストレージへのアクセス権限をユーザに許可してもらわないといけません。
ストレージにアクセス出来ないというのは致命的なので、この機能を実装する前にその部分を実装してください。
詳細は以下のサイトを参考にしてください。(時間が空けばこの部分も記事にします。)エクスポート編
では「エクスポート」から。
下記は別アプリケーションで保存先を指定するためのクラス。MainActivity.javapublic 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.javaprivate 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.javaprotected 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.javapublic 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.
インポート編は時間がある時に追記します。参考サイト