- 投稿日:2020-02-22T23:17:10+09:00
Flutter Form系Widgetの使い方 〜 すべてのWidgetを学習
はじめに
自身の勉強を兼ねて、
Widget Catalog
で公開されている全てのWidgetについての解説やソースコードを交えた使い方などをまとめています。基本方針として、掲載しているサンプルコードは公式サイトのサンプルを流用しています。しかし、公式サイトにはサンプルコードがないものが多くあるため、その場合には動作確認済みの自身のコードを掲載しています。間違い等ありましたらご指摘頂けると助かります。
カテゴリ Widgetの種類 Qiitaまとめ記事 Accesibility アクセシビリティに関するWidget Coming Soon Animation and Motion アニメーションに関するWidget Coming Soon Assets, Images, and Icons Asset/Icon/画像表示に関するWidget Coming Soon Async Asyncパターン (FutureBuilder, StreamBuilder) Coming Soon Basics 最低限抑えるべき基本的なWidget Coming Soon Cupertino (iOS-style widgets) iOS風の各種Widget Coming Soon Input フォームやキー入力等の入力に関するWidget 今回 Interaction Models タッチイベントやその他操作の応答に関するWidget Coming Soon Layout レイアウトに関するWidget Coming Soon Material Components Android マテリアルデザインの各種Widget Coming Soon Painting and effects ペイント, エフェクト効果等のWidget Coming Soon Scrolling GridView等のスクロール機能を保有するようなWidget Coming Soon Styling テーマ等のスタイリングに関わるWidget Coming Soon Text テキスト表示に関わるWidget Flutter Text Widgetの使い方 〜 すべてのWidgetを学習 Form系 Widgets
テキスト入力用途のフォームやキー入力検出に関する以下のWidgetについて解説します。
Widget名 用途 Form FormField用のコンテナWidgetで複数のFormFieldをまとめて管理する時に利用します。 TextFormField テキスト入力のFormField Widgetです。 FormField Form内で利用するベースとなるWidgetです。これを直接利用することはなく、TextFormFieldのように、FormFieldを継承して独自のFormFieldを作成する時に利用します。 RawKeyboardListener キー入力検出用のリスナーです。 Form
後述の複数の
FormFiled
Widgetをグループ化して管理するためのコンテナWidgetです。グループ化することで、ユーザが入力データの送信ボタンを押した時に、フォーム内の各入力データの形式チェックするValidator
を一括して呼び出すことが出来ます。Formクラスconst Form({ Key key, @required this.child, this.autovalidate = false, this.onWillPop, this.onChanged, }) : assert(child != null), super(key: key);
Form
は子クラス (後述のTextFormFiled
など) の最上位のコンテナとして定義します。なお、
Validator
を利用する場合には、第一引数のKey
にGlobalKey<FormState>
を指定する必要があります。このKey
については別の記事にて解説予定のため、ここでは以下のサンプルコードのように必要なものだレベルで覚えてるに留めておいてください。TextFormField
別途解説予定の
TextField
Widgetを後述のFormField
に適用したWidgetです。つまり、Form
におけるText入力のWidgetです。サンプルコード
サンプルとして、2つのテキスト入力を持ったフォームの例を示します。Submitボタンを押した時に各テキスト入力をチェックし、空白以外ならOKというロジックになっています。
サンプルコードclass FormSampleState extends State<WidgetSample> { final _formKey = GlobalKey<FormState>(); @override Widget build(BuildContext context) { return Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ TextFormField( decoration: const InputDecoration( labelText: "Email address form", // ラベル hintText: 'Enter your email', // 入力ヒント ), autovalidate: false, // 入力変化しても自動でチェックしない。trueにすると初期状態および入力が変化する毎に自動でvalidatorがコールされる validator: (value) { // _formKey.currentState.validate()でコールされる if (value.isEmpty) { return 'Please enter some text'; // エラー表示のメッセージを返す } return null; // 問題ない場合はnullを返す }, onSaved: (value) => () { // this._formKey.currentState.save()でコールされる print('$value'); }, ), TextFormField( decoration: const InputDecoration( icon: Icon(Icons.email), border: OutlineInputBorder(), // 外枠付きデザイン filled: true, // fillColorで指定した色で塗り潰し fillColor: Colors.greenAccent, labelText: "Email address form", hintText: 'Enter your email', ), autovalidate: false, validator: (value) { if (value.isEmpty) { return 'Please enter some text'; } return null; }, ), Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: RaisedButton( onPressed: () { // 各Fieldのvalidatorを呼び出す if (_formKey.currentState.validate()) { // 入力データが正常な場合の処理 this._formKey.currentState.save(); } }, child: Text('Submit'), ), ), ], ), ); } }FormField
FormField
はForm
内で利用するベースとなるWidgetです。後述のTextFormField
もこれを継承しており、その他Form
内で利用する独自定義Widgetを作成するなどに利用します。サンプル
サンプルとして、独自定義Widgetを利用したフォーム入力の例を示します。初期値0のカウンタを+/-ボタンで増減するフォームを作成しています。Submitボタンを押した時に-1以外であればResult表示を更新し、それ以外はFieldにエラーメッセージが表示されます。
サンプルコードclass FormFieldSampleState extends State<WidgetSample> { final _formKey = GlobalKey<FormState>(); int _count = 0; @override Widget build(BuildContext context) { return Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ CounterFormField( // 独自定義のFormField autovalidate: false, validator: (value) { if (value < 0) return 'Negative values not supported'; return null; }, onSaved: (value) => setState(() { _count = value; }), ), FlatButton( child: Text('Submit'), color: Colors.blue, onPressed: () { if (this._formKey.currentState.validate()) { this._formKey.currentState.save(); } }, ), SizedBox(height: 20.0), Text('Result = $_count') ])); } } // int型のデータを保有する独自FormLieldクラスを作成 class CounterFormField extends FormField<int> { CounterFormField( {FormFieldSetter<int> onSaved, FormFieldValidator<int> validator, int initialValue = 0, bool autovalidate = false}) : super( onSaved: onSaved, validator: validator, initialValue: initialValue, autovalidate: autovalidate, builder: (FormFieldState<int> state) { return Column(children: <Widget>[ Row( mainAxisSize: MainAxisSize.min, children: <Widget>[ IconButton( icon: Icon(Icons.remove), onPressed: () { // Fieldの値を変化させる state.didChange(state.value - 1); }, ), Text(state.value.toString()), IconButton( icon: Icon(Icons.add), onPressed: () { state.didChange(state.value + 1); }, ), ], ), state.hasError // Validatorの結果がエラー時の表示をここで対応 ? Text( state.errorText, style: TextStyle(color: Colors.red), ) : Container(), ]); }); }RawKeyboardListener
キーボード入力イベントをキャッチするためのWidgetです。iOS (かつエミュレータのみ?) 以外は問題なく動いてそうですが、iOS (かつエミュレータのみ?) だと反応が無いです。2016年の時点でissueが出ていますね。。
RawKeyboardListener doesn't receive any events from keyboard in ios simulatorサンプル
サンプルコードclass RawKeyboardListenerSampleState extends State<WidgetSample> { final FocusNode _focusNode = FocusNode(); String _message; @override void dispose() { _focusNode.dispose(); super.dispose(); } void _handleKeyEvent(RawKeyEvent event) { setState(() { _message = 'Key: ${event.logicalKey.debugName}, KeyId: ${event.logicalKey.keyId}'; }); } @override Widget build(BuildContext context) { return RawKeyboardListener( focusNode: _focusNode, onKey: _handleKeyEvent, child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ TextField(), Text(_message ?? 'Press a key'), ])); } }
- 投稿日:2020-02-22T19:48:11+09:00
iOS と Android で Opus 形式のオーディオを再生しようとしたらコンテナの違いにはまった話し
Android と iOS で Opus を扱うにあたって チョットワカッタ -> 完全に理解した(慢心)を20ループくらいしたので知見をメモします。
やりたかったこと
.wav
(の中のPCM)音源を品質と圧縮率のバランス良い形のフォーマットに変換して、Android (android.media.MediaPlayer
) と iOS (AVKit
) からよしなに再生したい。オーディオファイルのフォーマットとしては Opus が良さそうでこれに目を付ける。結論
Android 向けには Matroskaコンテナ、iOS 向けには CAFコンテナの中に
libopus
で圧縮した Opus 形式の音声データを格納すればよい。1ファイルで両プラットフォーム対応は無理なので諦めよう。FFmpeg を使う場合以下のようになる。
# for Android ffmpeg -i input.wav -vn -ac 2 -c:a libopus -b:a 24k output.mkv # for iOS ffmpeg -i input.wav -vn -ac 2 -c:a libopus -b:a 24k output.cafinput.wav が 140MB 程度で、エンコードしたあとは概ね 1MB程度。(上記コマンドのとおり ビットレートを 24k に指定した場合)
何が大変だったのか
Android は公式に Opus コーデック(のエンコード)をサポートしているが1、コンテナが適切でないと再生はできるけどシークが適切に行えない、などの問題があった。ローカルファイルだと行えてストリーミングだと無理、などの場合もあり問題の切り分けに苦労しつつ試行錯誤の末コンテナを Matroska にする必要がありそうなことに気付く(というか公式ドキュメントに書いてあった)。この間 .ogg, .oga, libopus, libvorbis などの組み合わせを無限に総当たりで試し続ける。
iOS は iOS11 から Opus コーデックをサポートするという話しがあったが結局どうなってしまったのかよくわかっていない。Androidでいけた
.mkv
ファイルがうまく再生できず詰んだ!ここは旧世代の遺物AACか〜 からの、afconvert
コマンドで適当に.caf
ファイルを作ってみたらなぜか再生できることに気づき、コンテナを CAF にする必要がありそうなことに気付く。ちなみに Opus と CAF については Wikipedia - Opus を見ると
(Limited container support)
という表記とともにOn iOS 11:Core Audio Format (.caf)
と書いてあるのでつまりそういうことなんだろう。ということはこの記事を書いていて気付きました。アウトプット大事...。何で Opus ?
音楽ファイルが96kbpsになる日――Opus音声コーデックの実力 - Qiita とか、YouTube で Opus vs MP3 で検索した結果 とかを参照すると良いと思います。感覚的な表現をすると、低ビットレートなMP3における音のこもり感が劇的に解消されている感じ。劇的に。これにより高品質で高圧縮なオーディオファイルをつくることができる。特に最新の技術ではない。
- 投稿日:2020-02-22T19:24:42+09:00
他のLiveDataをObserveできるMediatorLiveData
プロジェクト内でちょくちょくMediatorLiveDataを使っている場所があり、調べてみたのですが今一つ腑に落ちなかったので自分で触ってみました???
今回書いたコードはGithubに挙げてるのでこちらも参照してみてください??♂️
公式のMediatorLiveDataのページが完結でわかりやすかったのですが、MediatorLiveDataは 他のLiveDataをObserveできるLiveData です。
例えば、EditTextに入力された文字を表示しつつ、文字数も表示する、といったケースで考えてみます。
MainViewModel.ktval message: MutableLiveData<String> = MutableLiveData() val count: MediatorLiveData<Int> = MediatorLiveData()画面に表示するmessageは通常のMutableLiveDataで宣言しています。
このmessageを監視して、文字数をカウントするLiveDataをMediatorLiveData
として宣言してみました。countがmessageを監視する、と言うのは
addSource
を使ってこう書きます。MainViewModel.ktcount.addSource(message) { val cnt = message.value?.length ?: 0 count.postValue(cnt) }count.addSource(監視したい対象のLiveData) { // 監視したい対象のLiveDataが変更された時の処理 }これで、messageが変更されたときに自動で文字数がカウントされて、countにpostValueされるようになります。messageの変更は通常のLiveDataを同じように、更新用の関数を用意してあげて、View側から呼んであげる感じです。
MainViewModel.ktfun postMessage(message: String) { this.message.postValue(message) }MainViewModelの全体はこんな感じ。
MainViewModel.ktpackage com.github.yasukotelin.mediatorlivedatasample.ui.main import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel class MainViewModel : ViewModel() { val message: MutableLiveData<String> = MutableLiveData() val count: MediatorLiveData<Int> = MediatorLiveData() init { count.value = 0 count.addSource(message) { val cnt = message.value?.length ?: 0 count.postValue(cnt) } } fun postMessage(message: String) { this.message.postValue(message) } }実際に動かすとこんな感じで、入力された文字がmessageに流れると自動でcountが変更されます!
ちなみに、MediatorLiveDataを使わないで、messageを更新してから自分でcountも更新すれば同じじゃないの??と思うかもしれませんが、それだとうまく行きません。
MainViewModel.ktpackage com.github.yasukotelin.mediatorlivedatasample.ui.main import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel class MainViewModel : ViewModel() { val message: MutableLiveData<String> = MutableLiveData() val count: MutableLiveData<Int> = MutableLiveData() fun postMessage(message: String) { this.message.postValue(message) updateCount() } private fun updateCount() { val cnt = message.value?.length ?: 0 count.postValue(cnt) } }入力された文字数とカウントが合ってませんね。LiveDataの
postValue
はUIスレッドとは別スレッドで更新するメソッドです。そのため非同期で処理がされるため、countをmessageの値を使って更新しようと思うと同期が取れてなくて期待した動きにならないといったわけです。MediatorLiveDataを使えば、postValueで値が変更された後に発火するイベントを定義できるということがわかりました??♂️
さらにもうちょっと遊んでみます。
入力されたmessageが6文字以上だったらOKで、それ以外はNGにするといったよくあるやつをMediatorLiveDataを使って書いてみます。
プロパティは新しくcompleteMessageをMediatorLiveDataで追加しただけです。
MainViewModel.ktval message: MutableLiveData<String> = MutableLiveData() val count: MediatorLiveData<Int> = MediatorLiveData() val completeMessage: MediatorLiveData<String> = MediatorLiveData()監視の仕方はこんな感じにしてみます。
View -> messageを更新!
count -> messageを監視。自動で文字数を更新
completeMessage -> countを監視。6文字以上ならOKのメッセージ。MainViewModel.ktprivate val isCompleted: Boolean get() = count.value ?: 0 >= REQUIREMENT_WORDS init { count.value = 0 count.addSource(message) { val cnt = message.value?.length ?: 0 count.postValue(cnt) } completeMessage.addSource(count) { val msg = if (isCompleted) "Completed!" else "$REQUIREMENT_WORDS words required" completeMessage.postValue(msg) } } fun postMessage(message: String) { this.message.postValue(message) } companion object { private val REQUIREMENT_WORDS = 6 }MediatorLiveDataを使うことでなかなかにシンプルに書くことができました!
- 投稿日:2020-02-22T19:15:55+09:00
Android Mali GPU の Vulkan 制約のメモ
詳細は Sascha 先生謹製の Vulkan Caps Viewer でわかります.
Mali GPU(G-72)
- maxVertexInputAttributeOffset : 2047
- maxVertexInputBindingStride: 2048
- maxVertexInputAttributes: 16
- maxVertexInputBindings: 16
=> 頂点あたり, float で 512 個まで. Vertex input に頂点モーフ情報を入れる場合は, 50 個くらい(vertex, normal, tangent で 9 floats/target を想定)が限界.
- maxUniformBufferRange: 65536
Uniform buffer にデータを格納する場合は, 64KB まで. それ以上は SSBO や image buffer を使う.
- 投稿日:2020-02-22T17:38:14+09:00
AndroidでRecyclerViewを使ってみた
目的
- RecyclerViewを使ってみる。
- 題材:カレンダー(Grid形式)
ポイント
- RecyclerView.Adapterの下記のメソッドを実装するだけで、簡単に実装ができました。
- getItemCount()
- onCreateViewHolder(parent: ViewGroup, viewType: Int)
- onBindViewHolder(holder: DateAdapterHolder, position: Int)
実装内容
ActivityのonCreateでレイアウトを読み込み、RecyclerViewを生成します。
LayoutManagerは、GridLayoutManagerを設定し、カラム数はカレンダーのため7にします。RecyclerViewMainActivity.ktclass RecyclerViewMainActivity : AppCompatActivity() { private val SPAN_COUNT = 7 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_recycler_view) val recyclerView = findViewById<RecyclerView>(R.id.calendar) val adapter = DateAdapter(this) recyclerView.adapter = adapter // GridLayoutManagerを設定 // カレンダーなのでspanCount(カラム数)は7 val layoutManager = GridLayoutManager(this, SPAN_COUNT) recyclerView.layoutManager = layoutManager } }レイアウトは単純に、画面全体にRecyclerViewを設定します。
activity_recycler_view.xml<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.ykato.sample.kotlin.RecyclerViewMainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/calendar" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>続いては、Adapterです。
内部にViewHolderクラスを実装します。
今回Grid内には、日付用のTextViewを配置します。getItemCount()の戻り値は、固定で700としました。
※dateInfoListのサイズ(可変)にしようと思いましたが、getItemCountの値を途中で変えると色々と問題があったのでここでは固定値としています。DateAdapter.ktclass DateAdapter ( context: Context) : RecyclerView.Adapter<DateAdapter.DateAdapterHolder>() { private val ITEM_COUNT = 700 private val UPDATE_POSITION = 14 private val inflater = LayoutInflater.from(context) private val dateInfoUtil = DateInfoUtil() private var dateInfoList = dateInfoUtil.createDateInfoList() class DateAdapterHolder(view: View) : RecyclerView.ViewHolder(view) { var date = view.findViewById<TextView>(R.id.date) } override fun getItemCount(): Int { return ITEM_COUNT } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DateAdapterHolder { val view = inflater.inflate(R.layout.calendar_item, parent, false) return DateAdapterHolder(view) } override fun onBindViewHolder(holder: DateAdapterHolder, position: Int) { holder.date.text = dateInfoList[position].dateString holder.date.setTextColor(dateInfoList[position].textColor) if (dateInfoUtil.getDateInfoSize() - position < UPDATE_POSITION) { addDateInfo() } } private fun addDateInfo() { dateInfoUtil.addDateInfo() dateInfoList = dateInfoUtil.createDateInfoList() } }onCreateViewHolder(parent: ViewGroup, viewType: Int)では、レイアウトを読み込み、ViewHolderを生成して返します。
onBindViewHolder(holder: DateAdapterHolder, position: Int)ではpositionに応じた文字列をTexViewに設定します。
calendar_item.xml<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/calenderItem" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="right|center_vertical"> <TextView android:id="@+id/date" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" android:gravity="right" android:textAppearance="@style/TextAppearance.AppCompat.Small" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="日付" /> </android.support.constraint.ConstraintLayout>DateInfoUtilクラスは、カレンダーに表示する月、曜日、日付のリストを作成するクラスです。
下記のイメージの日付け等の情報を返すように実装しています。
1月 日 月 火 水 木 金 土 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 DateInfoUtil.ktclass DateInfoUtil { private val ADDITIONAL_COUNT_WEEK = 15 private var localDate = LocalDate.of(LocalDate.now().year, LocalDate.now().month,1) private var dateInfoList = ArrayList<DateInfo>() private val emptyDateInfo = DateInfo("") private val dayOfWeekArray = arrayOf(DayOfWeek.SUNDAY,DayOfWeek.MONDAY,DayOfWeek.TUESDAY,DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY,DayOfWeek.FRIDAY,DayOfWeek.SATURDAY) private fun addMonthHeader(localDate: LocalDate , calenderStr : ArrayList<DateInfo>) { if (isJanuary(localDate)) { addYearToDateInfoList(calenderStr, localDate) addMonthToDateInfoList(calenderStr, localDate) } else { addMonthToDateInfoList(calenderStr, localDate) addEmptyInfoToDateInfoList(calenderStr) } addEmptyInfoToDateInfoList(calenderStr, 5) } private fun isJanuary(localDate: LocalDate) = localDate.month == Month.JANUARY private fun addYearToDateInfoList(calenderStr: ArrayList<DateInfo>, localDate: LocalDate) { calenderStr.add(DateInfo(localDate.year.toString())) } private fun addMonthToDateInfoList(calenderStr: ArrayList<DateInfo>, localDate: LocalDate) { calenderStr.add(DateInfo(localDate.month.toString().substring(0, 3))) } private fun addEmptyInfoToDateInfoList(calenderStr: ArrayList<DateInfo>, count: Int = 1) { for (i in 1..count) { calenderStr.add(emptyDateInfo) } } private fun addWeekHeader(calenderStr : ArrayList<DateInfo>) { for (i in dayOfWeekArray) { val dateInfo = DateInfo(i.toString().substring(0, 3)) dateInfo.textColor = when(i) { DayOfWeek.SUNDAY -> Color.RED DayOfWeek.SATURDAY -> Color.BLUE else -> Color.BLACK } calenderStr.add(dateInfo) } } private fun addDateInfo(calenderStr : ArrayList<DateInfo>) { var isFirstDay = localDate.dayOfMonth == 1 for (i in 1..ADDITIONAL_COUNT_WEEK) { if (isFirstDay) { addMonthHeader(localDate, calenderStr) addWeekHeader(calenderStr) isFirstDay = false } for (j in dayOfWeekArray) { if (!isFirstDay && j == localDate.dayOfWeek) { val dateInfo = DateInfo(localDate.dayOfMonth.toString()) dateInfo.textColor = when(j) { DayOfWeek.SUNDAY -> Color.RED DayOfWeek.SATURDAY -> Color.BLUE else -> Color.BLACK } calenderStr.add(dateInfo) localDate = localDate.plusDays(1.toLong()) if (localDate.dayOfMonth == 1) { isFirstDay = true } } else { calenderStr.add(emptyDateInfo) } } } } fun createDateInfoList() : ArrayList<DateInfo> { addDateInfo(dateInfoList) return dateInfoList } fun getDateInfoSize() : Int { return dateInfoList.size } fun addDateInfo() { addDateInfo(dateInfoList) } }改善点
- 表示までのパフォーマンスが良くないので原因調査と改善が必要です。
- 月と曜日はスクロールしても残るように、ItemDecorationを使用してスティッキーヘッダーに修正します。
- 投稿日:2020-02-22T16:43:43+09:00
[Android]LeakCanaryによるメモリリークの検知
Androidアプリにおけるメモリリーク
Androidアプリ開発をしている上で、メモリリークって怖いですよね。
「Contextの扱いを誤るとメモリリークするよ!」って言われても
「そもそもContextってなんやねん!」って思われている方も多いのではないでしょうか。LeakCanary
開発中のアプリでRecyclerViewを使っていて、こちらの記事を拝見しました。
たまたま開発中のアプリでも同様の実装をしており、メモリリークが身近なものであるということを再認識したので
上記の記事でも紹介されているLeakCanaryを導入してみました。導入
appレベルのbuild.gradleに依存関係を追加するだけです。
古いバージョンではちょこっとソースコードを書く必要があったようですが、
最新バージョンではソースコードを書く必要すらありません。dependencies { def leakcanary_version = "2.2" // LeakCanary debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakcanary_version" }使い方
アプリ実行中するだけ。
メモリリークを検知するとアプリが通知してくれるようになります。
通知をタップすると、より詳細なダンプを確認できます。
めっちゃ便利。参考
- 投稿日:2020-02-22T15:13:57+09:00
【Android】RecyclerView+DataBindingでリストを作ったときに初期位置が読み込まれない
※初投稿です
※Android初学者の備忘録です
※原因はわかり次第追記予定表題の結論
XMLで
android:text="@{model.name}"のように指定すると、リストを表示したときに最初の1ページ分が表示されないことがある
=指定をなくすと表示される
(原因はよくわかってないです)発生した問題
RecyclerView + DataBinding + Moshiを使って、以下のようなアプリを作成しています
↑ 自作のJsonを読み込んでリストに起こしてます
ただし、XMLで@{model.name}みたいなかたちで指定すると
↑ のように最初の1ページ分が表示されません
(十分下までスクロールして戻ってくると表示される)なぜこうなるのか?
RecyclerViewに問題があるのか、DataBindingに問題があるのか、はたまたMoshiに問題があるのか
そこがイマイチよくわからず、結局どこに原因があるのかはわかりませんでしたただ、別の記事などを参考にするとXMLで指定をして上手く動作していることが多いので、
重いデータを読み込んでいる場合に起こるのだと思います
(でも以前WebAPIから読み込んだときはうまいことできてたし…?)原因は分かり次第更新しようと思います
- 投稿日:2020-02-22T04:28:47+09:00
FlutterのAndroidビルドで警告が発生したのでminSdkVersionを上げて解決した話
TerminalでAndroidビルドの実行
Flutterでアプリ開発していて、しばらくはiPhoneしか使っていなかったのですが、Pixel 3 XL を購入したのでAndroidでも実機実行しようと思いました。
とりあえずVS CodeのTerminalでAPK Buildすると…% flutter build apk警告発生
以下のエラーが赤く表示されました…
動きはするんですが気持ち悪いですよね?注意:/Users/riscait/development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_admob-0.9.0+10/android/src/main/java/io/flutter/plugins/firebaseadmob/AdRequestBuilderFactory.javaは非推奨のAPIを使用またはオーバーライドしています。 注意:詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。 注意:/Users/riscait/development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_core-0.4.3+1/android/src/main/java/io/flutter/plugins/firebase/core/FirebaseCorePlugin.javaは非推奨のAPIを使用またはオーバーライドしています。 注意:詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。 注意:/Users/riscait/development/flutter/.pub-cache/hosted/pub.dartlang.org/cloud_firestore-0.13.0+1/android/src/main/java/io/flutter/plugins/firebase/cloudfirestore/CloudFirestorePlugin.javaの操作は、未チェックまたは安全ではありません。 注意:詳細は、-Xlint:uncheckedオプションを指定して再コンパイルしてください。 注意:/Users/riscait/development/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_analytics-5.0.9/android/src/main/java/io/flutter/plugins/firebaseanalytics/FirebaseAnalyticsPlugin.javaは非推奨のAPIを使用またはオーバーライドしています。 注意:詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。 注意:/Users/riscait/development/flutter/.pub-cache/hosted/pub.dartlang.org/package_info-0.4.0+13/android/src/main/java/io/flutter/plugins/packageinfo/PackageInfoPlugin.javaは非推奨のAPIを使用またはオーバーライドしています。 注意:詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。 /Users/riscait/development/flutter/.pub-cache/hosted/pub.dartlang.org/shared_preferences-0.5.6/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java:25: 警告: [deprecation] FlutterPluginBindingのgetFlutterEngine()は非推奨になりました setupChannel(binding.getFlutterEngine().getDartExecutor(), binding.getApplicationContext()); ^ 警告1個 注意:一部の入力ファイルは非推奨のAPIを使用またはオーバーライドしています。 注意:詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。
- Firebase AdMob
- Firebase Core
- Firebase Cloud Firestore
- Firebase Analytics
- PackageInfo
- SharedPlugin
のプラグインで警告が発生しているようですね。
解決方法
/android/app/build.gradle ファイルを開き、1行編集します。
defaultConfig { applicationId "com.company.sample" minSdkVersion 23 // <-ここを16だったので23に上げました targetSdkVersion 28
minSdkVersion
を上げました。
最初は21に上げたのですが、警告に変化なかったので23に上げたらすべて解決しました!ライブラリによって設定すべき
minSdkVersion
があるんでしょうね。
どこかに書いてあるのかもしれませんが、軽く見た感じでは見つけることができませんでした?