20210911のAndroidに関する記事は5件です。

Jetpack Compose でキーボードを閉じる方法

LocalSoftwareKeyboardController.current.hide() を呼ぶことでキーボードを閉じることができる。 @OptIn(ExperimentalComposeUiApi::class) @Composable fun Greeting() { val keyboardController = LocalSoftwareKeyboardController.current var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }), ) } LocalSoftwareKeyboardController は実験的機能であるため、コンパイルエラーを回避するために @OptIn(ExperimentalComposeUiApi::class) を付ける必要がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DocumentsProviderについて調べた

概要 この記事は、AndroidのDocumentsProviderについての概要をまとめた内容です。 SMB/CIFSの共有フォルダにDocumentsProviderを介してアクセスするためのアプリ、「CIFS Documents Provider」を開発するに当たり、調べた内容になります。 Storage Access Framework とは Androidには、Android4.4より導入されたStorage Access Framework (SAF)という仕組みがあります。SAFは、ストレージサービスに対する共通のインターフェースを提供する仕組みです。このSAFを介することで、クライアント(アプリ)はローカルでもリモートでも共通のインターフェースを使って、ファイルにアクセスすることができるようになります。 DocumentsProviderとは DocumentsProviderとは、SAFにおいてファイルを提供する側です。ドキュメントでは以下のように書かれています。 サービスをカプセル化する DocumentsProvider を実装することで、クラウドやローカル ストレージ サービスをエコシステムに参加させることができます。プロバイダのドキュメントへのアクセスが必要なクライアント アプリは、数行のコードだけで SAF と統合できます。 DocumentsProviderを実装することでストレージサービスのアクセスを抽象化し、SAFの仕組みを使って同一のコードでディレクトリやファイルが扱えるようになります。これによってユーザがサービスの仕組みを意識せず、透過的にファイルを操作することができるようになります。 例えば、Google DriveやDropBoxといったストレージサービスのアプリをインストールすると、SAFを介して各サービスに保存されたファイルをクライアントから操作できるようになります。これは、各アプリがファイルにアクセスするためのDocumentsProviderを実装しているためです。 クラスの概要 SAFを扱う上でよく登場するのが次のようなクラスです。 DocumentFile DocumentFileは、SAFのクライアント側で扱われるファイルやディレクトリの抽象クラスとなります。ファイルやディレクトリとして扱うためのいくつかのプロパティ(名前やサイズ、更新日時など)やメソッドが用意されています。実装自体は、JavaのFileクラス(java.io.File)によく似ており、同じような使い方ができます(このDocumentFileは、Fileクラスからインスタンスを作成することもできますが、クラスやインターフェースを継承しているわけではありません)。 ファイルやディレクトリは、DocumentsProviderより発行されるURIによって指定します。URIにアクセスするためには、事前にDocumentsProvierからアクセス権限を取得しておく必要があります。 DocumentsProvider DocumentsProviderは名の通り、DocumentsProviderを定義するクラスです。DocumentsProviderは、クライアント側がDocumentFileで扱えるファイルの情報を返したり、DocumentProvider自身の情報を返したり、発行するURIとファイルのマッピングを行ったりします。 ちなみに、DocumentsProviderは従来から存在したContentProviderを継承しており、ContentProviderと同様にクエリを使う事もできるようです(使った事ないですが)。 余談ですが、Android 10以降では、Android端末のローカルコンテンツを管理するMediaStoreにDocumentsProvider用のカラムが追加されているため、MediaStoreのコンテンツURIから直接DocumentFileとして扱うことが可能となっています。 標準で備わっているDocumentsProvider Androidには、標準でいくつかのDocumentsProviderが備わっています。これらは、内蔵のストレージやSDカードにアクセスする際などに利用されます。次のようなDocumentsProviderが提供されています。(他にもあるかもしれないですが、よく知らないです。) 内蔵ストレージ/SDカード ( ExternalStorageProvider ) 端末の内蔵ストレージ、SDカードなどの端末のストレージへのアクセスを提供します。 メディアデータ ( MediaDocumentsProvider ) 端末に保存されたメディアファイルを管理するMediaStore上で扱うファイルへのアクセスを提供します。 ダウンロードデータ ( DownloadStorageProvider ) 端末でダウンロードされたファイルへのアクセスを提供します。 ピッカー クライアントがドキュメントプロバイダからファイルのアクセス権限を獲得し、DocumentFileの情報を取得するためには、SAFのピッカーを用いて、ユーザにファイルを選択させる必要があります。 Androidアプリで外部ファイルに対する操作を行う際、選択画面(ピッカー)を目にしたことがあるかと思いますが、これらはSAFの仕組みで提供されています。ピッカーでは、アプリから利用可能なDocumentsProviderと、それぞれが管理するファイルの一覧を表示し、ユーザはそこから目的のファイルやディレクトリを選択します。ピッカーでDocumentsProviderを選択する画面は次のようになります。 また、DocumentsProviderのピッカーには、3種類(ファイル読込み、ファイル書込み、ディレクトリ選択)があります。 ファイル読込み ファイル書込み ディレクトリ選択 クライアントアプリのActivityで、各々のピッカーで選択したファイル・ディレクトリのDocumentFileを作成する実装コードは次のようになります。現在では、従来のonActivityResultではなく、activity-ktxやfragment-ktxに含まれるregisterForActivityResultを用いて選択結果を受け取る実装が推奨されます。 ファイル読込み val openLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> uri ?: return@registerForActivityResult // 権限の永続化 contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) // その他、UR val documentFile = DocumentFile.fromSingleUri(this, uri) // 以降、ファイル読込み処理 } // 呼び出しコード例: openLauncher.launch(arrayOf("*/*")) ファイル書込み val createLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri -> uri ?: return@registerForActivityResult // 権限の永続化 contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) // DocumentFile作成 val documentFile = DocumentFile.fromSingleUri(this, uri) // 以降、ファイル書込み処理 } // 呼び出しコード例: createLauncher.launch("*/*") ディレクトリ選択 val treeLauncher = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri -> uri ?: return@registerForActivityResult // 権限の永続化 contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) // DocumentFile作成 val documentFile = DocumentFile.fromTreeUri(this, uri) // 以降、ディレクトリ処理 } // 呼び出しコード例: treeLauncher.launch(null) 選択したファイルやディレクトリの永続的な権限が必要な場合は、戻ってきたURIに対して、takePersistableUriPermission を実行する必要があります。 ただし、DocumentsProviderがこれらをすべてサポートしているとは限りません。例えば、Google DriveのDocumentsProviderはディレクトリの選択ができません。 さいごに ここでは、Storage Access Frameworkの仕組みと、簡単な概要について紹介しました。これを用いる事で、様々なストレージサービスを同じ操作で扱えるようになることが分かります。近年のAndroidは外部ストレージへのアクセスが厳しくなっており、今後アプリが共通のストレージにアクセスする場合は、SAFを用いる必要があるため、開発者はこれらの情報を認識しておく必要があるかと思います。 さらに、ここで調べた知識を踏まえた上で、CIFS Documents Providerというアプリを作成したのですが。それについてはまた別の機会に書きます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jetpack Composeでテキストの中にアイコンを埋め込んで表示させる

Jetpack Compose では簡単にテキストの中にアイコンを埋め込むことができたので紹介しようと思います。 実装コード 早速サンプルコードから @Composable fun Example() { val iconId = "iconId" Text( text = buildAnnotatedString { appendInlineContent(iconId) append("Hoge") appendInlineContent(iconId) append("Fuga") }, inlineContent = mapOf( iconId to InlineTextContent( placeholder = Placeholder( width = 24.sp, height = 24.sp, placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter, ), children = { Icon( painter = painterResource(id = R.drawable.ic_android), contentDescription = null, ) }, ), ), ) } 上記のコードを動かすとテキストの中にアイコンを表示させることができます。 テキストが長い場合でもアイコンがテキストの中に埋め込まれる形になるので、改行された後のテキストとアイコンのラインを揃えるデザインにも対応できます。 ざっくり実装を説明すると、 表示させたいアイコンのキーとなる文字列を定義 InlineTextContent にコンテンツを表示させる領域の指定と、表示させたいコンテンツの Composable を設定する Text の inlineContent に定義したキーと InlineTextContent を Pair で関連づけて渡す buildAnnotatedString の中で表示したいタイミングで appendInlineContent に定義したキーとなる文字列を渡すと Text の中で InlineTextContent で設定した Composable が表示される という感じになっています。 アイコン以外を表示する @Composable fun Circle( modifier: Modifier = Modifier, color: Color = MaterialTheme.colors.primary, ) { Canvas(modifier = modifier) { drawCircle( color = color, ) } } @Composable fun Example() { val iconId = "iconId" val circleId = "circleId" Text( text = buildAnnotatedString { appendInlineContent(iconId) append("Hoge") appendInlineContent(circleId) append("Fuga") }, inlineContent = mapOf( iconId to InlineTextContent( Placeholder( width = 24.sp, height = 24.sp, placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter, ), children = { Icon( painter = painterResource(id = R.drawable.ic_android), contentDescription = null, ) }, ), circleId to InlineTextContent( placeholder = Placeholder( width = 10.sp, height = 10.sp, placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter, ), children = { Circle( Modifier.size(8.dp) ) }, ) ), ) } 上記のコードを動かすと以下のようになり、Icon 以外でも表示させることができて、複数のコンテンツをテキストの中に表示させることができます。 アイコンが含まれるテキストの読み上げについて appendInlineContent() にはキーとなる文字列以外にも alternateText を設定できるようになっており、Talkback で読み上げる場合はこの alternateText が読み上げられます。 Text( text = buildAnnotatedString { appendInlineContent(iconId, "Android") append("Hoge") appendInlineContent(iconId) append("Fuga") }, ) このコードの場合は Android Hoge Fuga と読み上げられます。 コンテンツ側の Icon に contentDescription を設定した場合は、 contentDescription がテキストの読み上げられた最後にまとめて読み上げられます。 Text( text = buildAnnotatedString { appendInlineContent(iconId) append("Hoge") appendInlineContent(iconId) append("Fuga") }, inlineContent = mapOf( iconId to InlineTextContent( placeholder = Placeholder( width = 24.sp, height = 24.sp, placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter, ), children = { Icon( painter = painterResource(id = R.drawable.ic_android), contentDescription = "Android", ) }, ), ), ) このコードの場合は Hoge Fuga Android画像 Android画像 と読み上げられます。 そのため、appendInlineContent を使う場合のテキストの読み上げは、 appendInlineContent の alternateText で自然なテキストになるようにするか、Text 自体の semantics で設定してあげるのが良さそうです。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】+Buttonを長押して高速カウントアップ in Kotlin

➊ はじめに 「イベントのハンドリング」と「ハンドラによる周期処理」の基礎を勉強したので、これを用い、よくある「+Button長押して、高速カウントアップ」みないな UI を作ってみました。 ➋ どんな感じ? 百聞は一見にしかずということで、こんな感じ~になります。 (1) イメージ 「TextView」1個と「Button」3個のシンプルなアプリです? (2) アプリの仕様 +ボタン押下:カウントアップ +ボタン長押し:高速カウントアップ -ボタン押下:カウントダウン -ボタン長押し:高速カウントダウン CLEARボタン押下:カウント0クリア Acitivityを廃棄してもカウンタは引き継がれる カウンタは0未満にはならない ➌ お勉強ポイント 今まで勉強してきたものを組み合わせると、簡単にできちゃいます! Button押下イベントのハンドリング Handlerによる周期処理 データ保存方法 アクティビティのライフサイクル ➍ イベント処理方法 ただの1回のカウントアップなら、Click処理で良いです。また長押しを1回のみ検出するだけなら、LongClick処理で良いです。しかし、今回は、「長押し中に高速カウントアップを継続する」という処理を行うので、長押しを検出後、周期処理が必要となります。周期処理は、Handlerを (1) イベント処理整理 分かりやすくするために「+ボタン」のみで考えます。 イベントに対する処理は、こんな感じです。 これを組み合わせると、長押しでカウントを高速でカウントアップできます。 (2) 例えば 例えば、「+ボタン」を長押してから指を離した場合、以下のようになります。 LongClick検出で1回カウントアップし、その後指を離すまで、周期処理でカウントアップを行います。タイマが100msなので、カウントが高速カウントアップになります。その後、指を離したタイミングで、Runnableをクリアすれば周期起動が止まり、カウントアップが止まります。 ※LongClick検出後からRunnable送信のディレイを故意に1000msにしています。なので、実は、周期起動の1回目は1秒後にカウントアップし、その後は100ms毎にカウントアップするようにしています。ここはお好きに設定ください。 ➎ データ保存 Acitivityが破棄されるとデータも破棄されてしまいます。Acitivityのライフサイクルを確認しながら、Acitivityが破棄してもデータを戻せるように、データ保存の処理も付け加えました。データの保存、読出は以下の手順で実施可能です。 (1) データ保存 snipet var count : Int = 100 val dataStore : SharedPreferences = getSharedPreferences("DataStore", MODE_PRIVATE) dataStore.edit().putInt("count", count).apply() (2) データ読出し snipet val dataStore : SharedPreferences = getSharedPreferences("DataStore", MODE_PRIVATE) var count : Int = dataStore.getInt("count", 0) ➏ 実装 仕組みが分かったところで、上記をまとめ、諸々実装していきます。 言語は、「Kotlin」で実装します。 (1) strings.xml app/src/main/res/values/strings.xml strings.xml <resources> <string name="app_name">APP5</string> <string name="textView_label">0</string> <string name="buttonCountUp">+</string> <string name="buttonCountDown">-</string> <string name="buttonCountClear">Clear</string> </resources> (2) activity_main.xml app/src/main/res/layout/activity_main.xml activity_main.xml <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.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=".MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/textView_label" android:textSize="96sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.498" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.388" tools:ignore="MissingConstraints" /> <Button android:id="@+id/buttonCountClear" android:layout_width="124dp" android:layout_height="155dp" android:text="@string/buttonCountClear" android:textSize="24sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.773" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.627" /> <Button android:id="@+id/buttonCountDown" android:layout_width="124dp" android:layout_height="70dp" android:text="@string/buttonCountDown" android:textSize="34sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.237" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.667" /> <Button android:id="@+id/buttonCountUp" android:layout_width="124dp" android:layout_height="70dp" android:text="@string/buttonCountUp" android:textSize="34sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.237" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.56" /> </androidx.constraintlayout.widget.ConstraintLayout> (3) activity_main.kt app/src/main/java/com/poodlemaster/app5/MainActivity.kt MainActivity.kt package com.poodlemaster.app5 import android.annotation.SuppressLint import android.content.SharedPreferences import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Log import android.view.MotionEvent import android.view.View import android.widget.Button import android.widget.TextView class MainActivity : AppCompatActivity(), Runnable, View.OnClickListener, View.OnLongClickListener, View.OnTouchListener { companion object { const val UNDER_NULL = 0 const val UNDER_COUNT_UP = 1 const val UNDER_COUNT_DOWN = 2 } private var underUpDown = UNDER_NULL private var count : Int = 0 private val handler : Handler = Handler(Looper.getMainLooper()) //-------------------------------------------------------------------------------------- @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d("app5/status", "onCreate") setContentView(R.layout.activity_main) // ’+’ボタン val buttonCountUp : Button = findViewById(R.id.buttonCountUp) buttonCountUp.setOnTouchListener(this) // タッチ buttonCountUp.setOnClickListener(this) // クリック buttonCountUp.setOnLongClickListener(this) // ロングクリック // ’-’ボタン val buttonCountDown : Button = findViewById(R.id.buttonCountDown) buttonCountDown.setOnTouchListener(this) // タッチ buttonCountDown.setOnClickListener(this) // クリック buttonCountDown.setOnLongClickListener(this) // ロングクリック // ’CLEAR’ボタン val buttonCountClear : Button = findViewById(R.id.buttonCountClear) buttonCountClear.setOnClickListener(this) // クリック } //-------------------------------------------------------------------------------------- override fun onClick(view: View){ when (view.id) { // `+`をクリック R.id.buttonCountUp -> { Log.d("app5/onClick", "buttonCountUp") countUp() } // `-`をクリック R.id.buttonCountDown -> { Log.d("app5/onClick", "buttonCountDown") countDown() } // 'CLEAR'をクリック R.id.buttonCountClear -> { Log.d("app5/onClick", "buttonCountClear") countClear() } } } //-------------------------------------------------------------------------------------- override fun onLongClick(view: View) : Boolean { when (view.id) { // `+`をロングクリック R.id.buttonCountUp -> { Log.d("app5/onLongClick", "buttonCountUp") countUp() handler.postDelayed(this, 1000) } // `-`をロングクリック R.id.buttonCountDown -> { Log.d("app5/onLongClick", "buttonCountDown") countDown() handler.postDelayed(this, 1000) } } return(true) // trueはonClick処理しない。falseでonClick処理を続けて実行。 } //-------------------------------------------------------------------------------------- @SuppressLint("ClickableViewAccessibility") override fun onTouch(view: View, motEvent: MotionEvent): Boolean { when (view.id) { // '+'をタッチ R.id.buttonCountUp -> { when (motEvent.action) { MotionEvent.ACTION_DOWN -> { // 押す Log.d("app5/onTouch", "buttonCountUp(ACTION_DOWN)") } MotionEvent.ACTION_UP -> { // 離す Log.d("app5/onTouch", "buttonCountUp(ACTION_UP)") // Runnable解除 handler.removeCallbacks(this) } else -> { // else Log.d("app5/onTouch", "buttonCountUp(" + motEvent.action.toString() + ")") } } } // '-'をタッチ R.id.buttonCountDown -> { when (motEvent.action) { MotionEvent.ACTION_DOWN -> { // 押す Log.d("app5/onTouch", "buttonCountDown(ACTION_DOWN)") } MotionEvent.ACTION_UP -> { // 離す Log.d("app5/onTouch", "buttonCountDown(ACTION_UP)") // Runnable解除 handler.removeCallbacks(this) } else -> { // else Log.d("app5/onTouch", "buttonCountDown(" + motEvent.action.toString() + ")") } } } } return(false) // trueでコールバック処理終了。falseはコールバック処理を継続。 } //-------------------------------------------------------------------------------------- private fun countUp() : Int { underUpDown = UNDER_COUNT_UP // カウントアップ if(count < Integer.MAX_VALUE) { count++ } Log.d("app5/countUp(count)", count.toString()) val textView : TextView = findViewById(R.id.textView) textView.text = count.toString() return(count) } //-------------------------------------------------------------------------------------- private fun countDown() : Int { underUpDown = UNDER_COUNT_DOWN // カウントダウン if(count > 0) { count-- } Log.d("app5/countDown(count)", count.toString()) val textView : TextView = findViewById(R.id.textView) textView.text = count.toString() return(count) } //-------------------------------------------------------------------------------------- private fun countClear() { // カウントクリア count = 0 Log.d("app5/countClear(count)", count.toString()) val textView : TextView = findViewById(R.id.textView) textView.text = count.toString() } //-------------------------------------------------------------------------------------- private fun saveCount() { // データストア(書き込み) val dataStore : SharedPreferences = getSharedPreferences("DataStore", MODE_PRIVATE) dataStore.edit().putInt("count", count).apply() Log.d("app5/saveCount(count)", count.toString()) } //-------------------------------------------------------------------------------------- private fun readCount() { // データストア(読み取り) val dataStore : SharedPreferences = getSharedPreferences("DataStore", MODE_PRIVATE) count = dataStore.getInt("count", 0) Log.d("app5/readCount(count)", count.toString()) } //-------------------------------------------------------------------------------------- override fun run() { when(underUpDown){ UNDER_COUNT_UP -> { countUp() } UNDER_COUNT_DOWN -> { countDown() } } handler.postDelayed(this, 100) } //-------------------------------------------------------------------------------------- override fun onStart() { super.onStart() Log.d("app5/status", "onStart") } //-------------------------------------------------------------------------------------- override fun onResume() { super.onResume() Log.d("app5/status", "onResume") readCount() val textView : TextView = findViewById(R.id.textView) textView.text = count.toString() } //-------------------------------------------------------------------------------------- override fun onPause() { super.onPause() Log.d("app5/status", "onPause") saveCount() handler.removeCallbacks(this) } //-------------------------------------------------------------------------------------- override fun onStop() { super.onStop() Log.d("app5/status", "onStop") } //-------------------------------------------------------------------------------------- override fun onRestart() { super.onRestart() Log.d("app5/status", "onRestart") } //-------------------------------------------------------------------------------------- override fun onDestroy() { super.onDestroy() Log.d("app5/status", "onDestroy") } } ➐ 以上 とりあえず、今回は、ほぼ今まで勉強してきたものでなんとかなりました~?? 今回は周期処理させるものが軽い処理(count++)のみでしたので、UIスレッド(メインスレッド)上のLooperへRunnableをQueueingしましたが、重い処理を実行する場合は、これをUIスレッド(メインスレッド)でやると反応が遅くなり、ユーザビリティが失われてしまいます。そのため、重い処理を実行する場合は、バッググラウンドで動作しているスレッドにリクエストを行うようにします。今後は、マルチスレッドも勉強していきたいと思っています。 「Android Studio」でkotlin言語を使って簡単なアプリを作れたら楽しいだろうなと思って少し使い始めました。まずは、機能の基礎勉強中です。同士の役に立てれば本望です? お疲れ様でした?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Flutter】社内向け入門講座の内容公開

インドネシアのPT.AQ Business Consulting IndonesiaでFlutterのエンジニア兼アドバイザーをしております菊池と申します。 Zennで本を出版しておりますflutter chips(30,000字程度) 本記事で伝えること Flutterを用いた簡単なアプリ開発デモ 開発効率をあげるTips デバッグ方法 おすすめパッケージ アーキテクチャ紹介 やっておいたほうが良いこと 知っておいて損しないこと 情報収集源 本記事を読む前に 本記事を読む前提として以下を実施済みですと、より内容が理解しやすいかと思います。 - Flutterの環境構築 以下の弊記事を参考にして環境構築を進めていただければと思います。 【Flutter】アプリ開発入門 Flutter環境構築 Hello Flutter!! 簡単なデモ webviewアプリ作成 ソースコード 開発効率あげる方法 AndroidStudio プラグイン 詳細な内容は弊記事【Flutter】モバイルアプリ高速開発プラットフォームをさらに爆速化するTipsを参照ください。 おすすめプラグイン FlutterSnippets Flutter Enhancement Suite Flutter Pub Version Checker flutter-img-sync FlutterJsonBeanFactory 静的解析 詳細な内容はこちらの記事Dart/Flutter の静的解析強化のススメを参照してください。 実装方法 インストール pedantic_monoパッケージをインストール。 pubspec.yaml dev_dependencies: pedantic_mono: any analysis_options.yamlの配置 https://github.com/mono0926/pedantic_mono/blob/master/example/analysis_options.yaml をプロジェクトのルートに置く。以上。 デバッグ方法 break point break pointを用いて、ソースコード実行中の途中で停止させることで、各変数に格納されている値を確認できます。デバッグ時に必須の手法になります。以下の記事などで詳しく紹介されています。 debug tool 詳細は弊記事【Flutter】インターフェース開発に強いFlutterで、さらにUI開発を加速させるテクニックを参照してください。 おすすめパッケージ flutter_svg SVG画像をFlutter内で扱う際に必須のパッケージ。 アイコンなどはPNG等のピクセル画像で入れると画像がボケたりしてしまうので、極力ベクター画像を使うようにしましょう。 auto_size_text テキストのサイズを動的に変更。端末の横幅によって収まらない場合や長いテキストの入力値に対しても、ほとんど意識せずに適応してくれる。 やっておいたほうが良いこと FVMでのFlutterのバージョン管理 Flutterはアップデートが頻繁に行われ、複数人でプロジェクトを進行する上では、Flutterのバージョンを合わせて開発する必要があります。 その上で、Flutterのバージョンをローカル上に複数共存させて、簡単に切り替えを行えるようにできるツールがFVMです。 ↓↓↓使用方法は弊記事を参考にしてください↓↓↓ 情報収集源 公式関連 monoさん Medium 知っておいても良いこと DartPad DartやFlutterのコードをブラウザ上で実行できるサイト。 ちょっとしたコードの確認などを行う際に便利です。 クローンアプリ系 TwitterやWhatsApp,Slackなどのクローンアプリを作成して公開してくれている方がいます。 参考にできる点も多いかと。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む