- 投稿日:2020-03-25T22:46:49+09:00
Flutter で外部ストレージにアクセスするアプリを作るときは Android バージョンを考慮して『ひと手間』いるから注意してくれよな!
TL;DR
READ_EXTERNAL_STORAGE
パーミッションやWRITE_EXTERNAL_STORAGE
パーミッションを必要としているアプリは、AndroidManifest.xml
にandroid:requestLegacyExternalStorage="true"
を設定すれば OK です。app/src/main/AndroidManifest.xml<application android:name="io.flutter.app.FlutterApplication" android:label="YourAppName" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">事の発端
開発中のアプリで image_picker を使っていました。
エミュレーターでは問題なく動作していたのですが、いざ実機で動作確認をしてみると……「イメージをピックできない!?」
となったわけです。
実はエラーが発生していた
デバッグして確認してみると、
Unable to decode stream: java.io.FileNotFoundException: /storage/emulated/0/Pictures/any_image.jpg: open failed: EACCES (Permission denied)
というエラーが発生していました。
パーミッション? と思い
AndroidManifest.xml
に<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
を設定してみましたが結果は変わりませんでした。原因
じつは Issues にあがっていました。
Android バージョンが原因のようです。変更されていた外部ストレージへのアクセス方法
Android 10 ( API レベル 29 )以降から外部ストレージへのアクセス管理が変更 1 になっているようです。
ユーザーがファイルを詳細に管理して整理できるように、Android 10(API レベル 29)以降をターゲットとするアプリには、外部ストレージ デバイスに対する特別アクセス権限がデフォルトで付与されます(対象範囲別ストレージ)
とのことで、
READ_EXTERNAL_STORAGE
パーミッションやWRITE_EXTERNAL_STORAGE
パーミッションは不要になっています。また、他のアプリが作成したファイルへのアクセスは、
- アプリに
READ_EXTERNAL_STORAGE
パーミッションが付与されていること。- 対象ファイルが、明確に定義された次のいずれかのメディア コレクション内にあること。
- 写真 - 保存場所:
MediaStore.Images
- 動画 - 保存場所:
MediaStore.Video
- オーディオ ファイル - 保存場所:
MediaStore.Audio
のようになっています。
つまり、 Flutter のパッケージがこの対応についていっていないと外部ストレージにアクセスできない状況が発生するということです。
対策
Android デベロッパー に次のような警告があがっていました。
つまり、いずれきれいにしたいけど今は微妙な状態だから気をつけてな! ってことでしょうね。
だから上で紹介した Issues もオープン状態のままなわけで……。そんな状態でも一応は対処方法があるようなので紹介します。
対象範囲別ストレージをオプトアウトする
Android 9( API レベル 28 )以前をターゲットにする
これはそのとおりですね。
アクセス管理変更前のバージョンを相手にすればエラーは起こりません。
requestLegacyExternalStorage
の値をtrue
に設定するAndroid 10 以降をターゲットにしている場合は、
AndroidManifest.xml
にandroid:requestLegacyExternalStorage="true"
を設定すれば OK です。app/src/main/AndroidManifest.xml<application android:name="io.flutter.app.FlutterApplication" android:label="YourAppName" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">両方のバージョンをターゲットにしたい場合は?
app/src/main/AndroidManifest.xml<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="18" />
uses-permission
にmaxSdkVersion
属性が追加されたようなので、18
を指定すれば良さそうです。
(ただ Flutter で利用している分にはmaxSdkVersion
属性を指定してなくても動きそうなんだよな…… )まとめ
Flutter の開発をしていると、エラーの解消に割と時間がかかります。
プラットフォーム依存のものが多かったり、そもそも情報が少ないのも理由かもしれません。自分が遭遇したエラーで情報の少ないものはなるべく書き溜めていこうと思いますので、同じエラーで困っている人のお役に少しでも立てれば幸いです!
変更内容の詳細は Android デベロッパー に詳しく記載されています ↩
- 投稿日:2020-03-25T21:33:29+09:00
Roomのリレーションで悩んでいたら、リレーション実現しなくても実装出来た
Roomで1対1対応のリレーションを実現したい
Roomを使ったアプリケーションで、別々のテーブルの情報が両方必要なので、昔Railsを触った自分としては、
「ついにAndroidのRoomでもリレーションを実現しなければならない時が来た」
と思いました。
そこでドキュメントなど、色々見ていたのですがRoomで1対1対応のリレーションは、ズバリこう実装すれば良いというのがイマイチ分からないでいました。
ActiveRecordでいうhas_oneのような物は無いのでしょうか。ところが「参照するだけなら、そもそもRoomでリレーション組まなくても実装できる」という話を聞いて、自分は最初よく理解出来ませんでした。
DBでリレーション使わず、実現した方法
今回はサンプルとして、仮にゲームのQuestionテーブルとScoreテーブルとします。
// データモデルQuestion id: Int level: Int number: Int question: String created_at: String// データモデルScore id: Int level: Int number: Int time: Int score: Int clear_date: Stringこの2つを関連づけたいとします。このQuestionテーブルに対応したScoreを結び付けたい。必要な情報を抽出して、
// QuestionとScoreをまとめて扱う、QuestionAndScoreクラス class QuestionAndScore { companion object { fun createQuestionAndScore(level: Int, number: Int, question: String): QuestionAndScore { return QuestionAndScore().apply { this.level = level this.number = number this.question = question } } } var level: Int = 0 var number: Int = 0 var question: String = "" var time: String = "" }2つのテーブルの情報を同時に扱うQuestionAndScoreクラスを作成しました。
- 今回はQuestion情報に対する最高スコアを組み合わせた問題リストを作成することにします。
val allQuestions = getAllQuestions() //全ての問題データを取得する val allScores = getAllScores() //全てのスコアデータ取得 // 問題データとスコアデータを組み合わせたリスト val allQuestionsAndScores = mutableListOf<QuestionAndScore>().apply { allQuestions.forEach { questionData -> val level = questionData.level val number = questionData.number val question = questionData.question val questionAndScore = createQuestionAndScore(level, number, questoin) // 関連するスコアだけを取り出す val scoreList = allScores.filter { scoreData -> scoreData.level == level && scoreData.number == number } // 最高スコアを取得 val maxScore = scoreList.maxby { it.score }!!.score questionAndScore.score = maxTime add(questionAndScore) } }これで問題情報と各問題の最高スコアを組み合わせた問題リストが作成出来ました。
値を参照するだけなら、このように実現することが出来ました。
- 投稿日:2020-03-25T20:05:00+09:00
FlutterでWidgetの位置とサイズを取得する
FlutterでWidgetの位置やサイズを取得したい
検索しても地図上の位置を取得するのばっかヒットしてつらい
GlobalKeyを使ってRenderBoxを取得する
参考サイト:https://medium.com/@diegoveloper/flutter-widget-size-and-position-b0a9ffed9407
TestState.dart//class TestWidgetは省略 GlobaleKey globaleKey = GlobalKey(); //←これが重要 class _TestState extends State<TestWidget>{ @override Widget build(BuildContext context) { return Column( children: <Widget>[ Expanded( child: RaisedButton( child: Text("test"), onPressed: (){ //↓変数はRenderBoxで宣言(.findRenderObject()で帰ってくるのは"RenderObject"のため RenderBox box = globalKey.currentContext.findRenderObject(); print("ウィジェットのサイズ :${box.size}"); print("ウィジェットの位置 :${box.localToGlobal(Offset.zero)}"); }, ), ), Center( child: Text("このウィジェットのサイズ", key: globalKey, //←知りたいWidgetにGlobalKeyをセット ), ), ], ); } }RenderBox box = globalKey.currentContext.findRenderObject();
↑
これでGlobalKeyをセットしたWidgetを元に描画されたRenderBoxインスタンスを取得出来ます。(返ってくるのはRenderObjectなので変数の宣言はRenderBoxにする)結果
ウィジェットのサイズ :Size(88.0, 48.0)
ウィジェットの位置 :Offset(0.0,24.0).localeToGlobal(Offset)で取得している位置は、ウィジェットの左上の点
引数のOffsetがゼロでない場合、その分の座標が足される注意点
GlobalKeyを付けたWidgetが一度もBuildされていない場合、RenderBoxは取得出来ない
一度もWidgetがbuildされていない場合、RenderBoxはそもそも描画されていないので取得出来ません。(大きさが可変のWidgetを考えてみれば分かる)//失敗するやり方 GlobaleKey globaleKey = GlobaleKey(); class _TestState extends State<TestWidget> { //サイズと位置を取得するメソッド String _getLocaleAndSize() { RenderBox box = globalKey.currentContext.findRenderObject(); return "ウィジェットのサイズ :${box.size}\n" "ウィジェットの位置 :${box.localToGlobal(Offset.zero)}"; } @override Widget build(BuildContext context) { return Column( children: <Widget>[ Expanded( child: RaisedButton( child: Text(_getLocaleAndSize()),//←ボタンのタイトルにする onPressed: () { print(_getLocaleAndSize); }, ), ), Center( child: Text( "このウィジェットのサイズ", key: globalKey, ), ), ], ); } }結果
エラー
The method 'findRenderObject' was called on null.
Receiver: null
Tried calling: findRenderObject()回避法
WidgetsBinding.instance.addPostFrameCallback(Function callback)を使う
WidgetsBinding.addPostFrameCallbackを使うとBuild終了時に実行する処理が書けますvar globalKey = GlobalKey(); class _TestState extends State<TestWidget> { String _getLocaleAndSize() { RenderBox box = globalKey.currentContext.findRenderObject(); return "ウィジェットのサイズ :${box.size}\n" "ウィジェットの位置 :${box.localToGlobal(Offset.zero)}"; } String _text;//←変数を用意 @override Widget build(BuildContext context) { if (_text == null)//Build時、テキストがnullの場合↓を実行 WidgetsBinding.instance.addPostFrameCallback((cb){ setState(() { _text = _getLocaleAndSize(); }); }); return Column( children: <Widget>[ Expanded( child: RaisedButton( child: Text(_text ?? "テキストはまだない"), onPressed: () { print(_getLocaleAndSize); }, ), ), Center( child: Text( "このウィジェットのサイズ", key: globalKey, ), ), ], ); } }注意
WidgetsBinding.addPostFrameCallbackは他にも色々使えて便利ですが、build時に使用するときは無限ループしないように気をつけましょうね~
- 投稿日:2020-03-25T19:21:54+09:00
【Android】Android 11から始めるIME Transitions
IME Transitionsとは
Android 11 DP2から追加されたWindowInsets APIの一つです。
https://android-developers.googleblog.com/2020/03/android-11-developer-preview-2.htmlさっくり作ってみた
MainActivity.ktimport android.graphics.Insets import android.os.Bundle import android.view.* import androidx.appcompat.app.AppCompatActivity import androidx.core.view.updateLayoutParams import androidx.core.view.updateMargins import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { private val spacing36 by lazy { applicationContext.resources.getDimensionPixelOffset(R.dimen.spacing_36) } private val spacing16 by lazy { applicationContext.resources.getDimensionPixelOffset(R.dimen.spacing_16) } private val listener = object: WindowInsetsAnimationControlListener { override fun onCancelled() { animationController = null isFirst = true } override fun onReady(controller: WindowInsetsAnimationController, types: Int) { animationController = controller } } private var animationController: WindowInsetsAnimationController? = null private var isFirst = true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) window.setDecorFitsSystemWindows(false) val callback = object: WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { override fun onProgress( insets: WindowInsets, animations: MutableList<WindowInsetsAnimation> ): WindowInsets { text_input_layout.updateLayoutParams<ViewGroup.MarginLayoutParams> { updateMargins(bottom = insets.getInsets(WindowInsets.Type.ime()).bottom + spacing16) } return insets } } text_input_layout.setWindowInsetsAnimationCallback(callback) scroll_view.setOnScrollChangeListener { view, x, y, oldX, oldY -> if (isFirst && y != 0) { isFirst = false text_input_layout.windowInsetsController?.controlWindowInsetsAnimation( WindowInsets.Type.ime(), -1, null, listener ) } animationController?.setInsetsAndAlpha(Insets.of(0, 0, 0, scroll_view.scrollY - spacing36), 1f, 0f) } } }レイアウトファイルはScrollViewのなかにTextInputLayoutを追加すれば動く。
所感
その他にも、
inputLayout.windowInsetsController.hide() or show()
でキーボードの表示切り替えができます。
また、サンプルコードではTextInputLayoutにFocusが当たってるときと当たっていない時の制御を入れてないので、そこらへんの実装をちゃんとすればより良いUIになりそうです。※DP2のため今後変更の可能性があります。
参考
https://android-developers.googleblog.com/2020/03/android-11-developer-preview-2.html
https://developer.android.com/reference/kotlin/android/view/WindowInsetsAnimation.Callback
https://developer.android.com/reference/android/view/WindowInsetsAnimationController
- 投稿日:2020-03-25T18:43:59+09:00
FlutterでSplashスクリーンを設定する
SplashScreenとは?
アプリを起動させた時にアイコンが中央に表示される画面のことです。
これがあるだけで、大分アプリっぽくなりますよね?
実装にはiOSとAndroidで異なる手順を踏まなくてはいけなくなるので、
それぞれについて説明していきます。iOS
iOSは比較的簡単な手順で済みます。
1. project/ios/Runner/Assets.xcassets/LaunchImage.imageset/配下に画像を保存
2. project/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.jsonを編集
以上の2つのみです。画像を保存
この時、画像は1x, 2x, 3xの3つの倍率を用意しなければいけません。
カスタムで4xの倍率を用意してもいいみたいです。Contents.jsonの編集
Contents.jsonを以下のコードに書き換えます。
{ "images" : [ { "idiom" : "universal", "filename" : "画像ファイル名.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "画像ファイル名@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "画像ファイル名@3x.png", "scale" : "3x" }, { "idiom" : "universal", "filename" : "画像ファイル名@4x.png", "scale" : "4x" } ], "info" : { "version" : 1, "author" : "xcode" } }Android
Androidは結構苦戦しました?
その時遭遇したエラー対処方法なども一緒に載せておくので、合わせてみていただけると
問題なくできると思います。
1. project/android/app/src/main/res/配下に画像を保存
2. project/android/app/src/main/res/values/styles.xmlを編集
3. project/android/app/src/main/res/drawable/launch_backgound.xmlに追加
以上の3つの手順になります。画像を保存する
AndroidはiOSと異なり、hdpi,mdpi,xhdpi,xxhdpi,xxxhdpiの5つの倍率を用意しなければいけません。
また、{フォルダ名}-hdpi/{画像ファイル名} のように、それぞれの倍率をディレクトリごとで分けなければいけないので少し面倒です?
ここで画像ファイル名を決める時に、大文字と-(ハイフン)は使用できないので注意してください!
a-zと0-9と_(アンダースコア)のみになりますので、iOSの方で画像名に大文字を使用している場合は変更が必要になります。styles.xmlを編集
styles.xmlを以下のコードに書き換えます。
カラーコードが書かれているところは背景色の色なので、好きな色に変更することができます。<?xml version="1.0" encoding="utf-8"?> <resources> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> <item name="android:windowBackground">@フォルダ名/launch_background</item> </style> <!-- #FFFFFFはカスタム可能です--> <color name="background">#FFFFFF</color> </resources>launch_background.xmlを編集
android:drawableを@color/backgroudに置き換えます。
これによりstyles.xmlで指定した背景色に変更することができます。
また、新たにitemタグを追加してください。... <item android:drawable="@color/background"/> <item android:drawable="@フォルダ名/画像ファイル名" android:gravity="center" /> ...エラー対処
adb: failed to install /Users//Desktop/flutter_sample/build/app/outputs/apk/app.apk: Failure [INSTALL_FAILED_INSUFFICIENT_STORAGE] Error launching application on Android SDK built for x86.
実行しようとすると上記のエラーが出て、デバッグできないようなことがありました。
これは、エミュレータのStorageがいっぱいになっているのが原因でした。
- エミュレータの不要なアプリを消す
- 新規のエミュレータを作成する
このどちらかを行えば解決しました。
SplashScreenとは関係のないエラーですが、参考までに記述しておきます。Splashスクリーンの表示アイコンが大きすぎる
もう一つはAndroidのみで起きた現象ですが、用意したアイコンが大きすぎたのか、画面いっぱいにアイコンが表示されてしまいました。
この対処にはlaunch_background.xmlを追加で編集する必要があります。
先ほど追加したitemタグの要素にwidthとheightを指定してあげるといい感じに修正できました?<item android:width="200dp" <--追加 android:height="200dp" <--追加 android:drawable="@mipmap/launch_splash" android:gravity="center" />まとめ
Androidでやけに苦戦しまいましたが、なんとか実装ができました!
次回はアプリアイコンについてやろうかなと思います?
誰かのお役に立てたら嬉しいです?✨
それではまた!参考文献
https://qiita.com/shinki_uei/items/c0b9b9a6d25e280c7bec
https://www.developerlibs.com/2018/07/flutter-how-to-fix-white-screen-on-app.html
https://miajimyu.hatenablog.com/entry/2019/09/30/212723
- 投稿日:2020-03-25T17:25:03+09:00
【初心者向け】スマホでPCの画面を見る方法【iPhone/Android】
パソコン画面をiPhone/Androidに表示する最適な方法
iPhone/Androidの画面をパソコンにミラーリングして表示させる方法を知っている人が多いようですが、
パソコンをスマホにミラーリングする方法を知る人は多くではありません。では、この記事はパソコン画面をiPhone/Androidに表示する最適な方法について、ご紹介します。
事前準備
iPhone/Androidスマホ(この記事ではiPhoneを例として説明します。)
Windows PC(Windows7/8/8.1/10)
画面ミラーリングアプリ(この記事ではLetsView という無料のアプリを例として操作します。ApowerMirrorというミラーリングアプリもおすすめです。)操作手順
1.スマホとパソコンを同じWi-Fiネットワークに接続しておきます。
2.スマホとパソコンにLetsView をダウンロードして、起動します。(両方ともインストールしてください。)
LetsViewダウンロード
3.iPhone側で検出されたデバイスのリストが見えます。
※デバイスが検出されていない場合、「再検出」をタップします。
4.お使いのパソコンを選択します。
5.「PC画面ミラーリング」を選択します。
6.パソコン側から「許可」を選択します。
上記の手順に従うだけで、パソコンの画面をiPhoneにミラーリングして表示させることができます。
- 投稿日:2020-03-25T16:25:21+09:00
【Android】画面の表示サイズ(dpi)変更に耐えられるLayoutを定義する その2
AutosizingTextView について
AutosizingTextView の詳細については以前投稿したもを参考にしてください
画面の表示サイズ(dpi)変更に耐えられるLayoutを定義する - QiitaAutosizingTextView を APIレベル 26 未満で使う
AutosizingTextView は Android 8.0(API レベル 26)以降で使用できるようになった機能である
今回の目的は APIレベル 26 未満でも AutosizingTextView の自動サイズが適用される Layout を投稿することである
APIレベル 26 未満でも簡単に使用できたのでメモしておく普通の TextView でのレイアウト崩れの例
・3行に改行されてしまうTextViewがあったとする
・ここの文字列は可変であり何がくるかわからないとする(3文字くらいの単語が入ることも想定)
・短い単語では表示に問題はない
・横幅は変えれないような仕様となっている
・長い文字列の時に表示が崩れたくない要望があるどんな文字列が来ても表示が大丈夫なように作っておきたい
layout.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" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".MainActivity" tools:showIn="@layout/activity_main"> <Button android:id="@+id/button1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="button1" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/view1" app:layout_constraintHorizontal_chainStyle="spread" app:layout_constraintHorizontal_weight="4" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/view1" android:layout_width="0dp" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="Hello World" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/button2" app:layout_constraintHorizontal_weight="1" app:layout_constraintStart_toEndOf="@id/button1" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button2" android:layout_width="0dp" android:layout_height="wrap_content" android:text="button2" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_weight="4" app:layout_constraintStart_toEndOf="@id/view1" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>AutosizingTextView で解決
AutosizingTextView を使うといい感じに1行で自動的に収まるようになる
layout.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" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".MainActivity" tools:showIn="@layout/activity_main"> <Button android:id="@+id/button1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="button1" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/view1" app:layout_constraintHorizontal_chainStyle="spread" app:layout_constraintHorizontal_weight="4" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/view1" android:layout_width="0dp" android:layout_height="wrap_content" android:autoSizeMaxTextSize="20sp" android:autoSizeMinTextSize="1dp" android:autoSizeTextType="uniform" android:gravity="center_horizontal" android:maxLines="1" android:text="Hello World" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/button2" app:layout_constraintHorizontal_weight="1" app:layout_constraintStart_toEndOf="@id/button1" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button2" android:layout_width="0dp" android:layout_height="wrap_content" android:text="button2" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_weight="4" app:layout_constraintStart_toEndOf="@id/view1" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>APIレベル 26 未満では適用されない
上のレイアウトと同じ実装で Andord 6 といった APIレベル 26 未満のOSでアプリを見てみるとリサイズされていない
Android 6 や 7 のシェアもまあまあ高いのでなんとかしたいSupport Library で解決
Support Library を使うだけで下位のOSでも自動でリサイズできるようになる
今は AndroidX に Support Library が含まれるので AndroidX で定義してあげたほうが望ましい
https://developer.android.com/topic/libraries/support-librarylayout.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" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".MainActivity" tools:showIn="@layout/activity_main"> <Button android:id="@+id/button1" android:layout_width="0dp" android:layout_height="wrap_content" android:text="button1" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/view1" app:layout_constraintHorizontal_chainStyle="spread" app:layout_constraintHorizontal_weight="4" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <!-- android.support.v7.widget.AppCompatTextView --> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/view1" android:layout_width="0dp" android:layout_height="wrap_content" android:gravity="center_horizontal" android:maxLines="1" android:text="Hello World" android:textSize="20sp" app:autoSizeMaxTextSize="20sp" app:autoSizeMinTextSize="1dp" app:autoSizeTextType="uniform" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/button2" app:layout_constraintHorizontal_weight="1" app:layout_constraintStart_toEndOf="@id/button1" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button2" android:layout_width="0dp" android:layout_height="wrap_content" android:text="button2" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_weight="4" app:layout_constraintStart_toEndOf="@id/view1" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>※ 「android:」 で定義されているところを 「app:」 に変えないとリサイズ効果が出ないので注意すること
- 投稿日:2020-03-25T13:06:32+09:00
Scratch filesで気軽にコードの実行結果を確認する
Scratch filesとは
手軽にKotlinやJavaなどを実行できるファイルだよ。
用途的に近いものとしてpaiza.io等のオンラインエディタだけど、モジュール内で定義したクラスやコード補完を使えるといった違いがあるよ。Scratch filesの特徴
- モジュール内で定義したクラスを使用できる
- Interactive modeでコードを書き換えても即反映してくれる
- コード補完がある
- プロジェクトフォルダと違う場所にファイルが作成されるのでGitに追加されない
Scratch Fileの作成
New
->Scratch File
を選択Kotlin
を選択Scratch Fileの使い方
適当に処理を書いて
Run Scratch File
を押すと、右側に処理の実行結果が表示されます。(Interactive modeがONになっていると2秒毎に処理が実行される)
Use classpath of module
をapp
に選択するとモジュール内で作成したクラスが使用できるようになります。
おわりに
今までオンラインエディタでコードを試していたのですが、モジュール内で定義したクラスやコード補完がしっかりしている点でScratch files良きだなと思いました。
- 投稿日:2020-03-25T02:47:40+09:00
Retrofit2で共通処理を行う
AndroidアプリでAPIを叩きたい時、定番のライブラリと言えばRetrofitです。
このRetrofitを使って、APIからのレスポンスによって同じ様な処理を行う場合、毎回同じコードを書くのは面倒ですよね!
同じ様な処理は共通化しちゃいましょう!通常のAPI呼び出し
例えば、エラー時に下記のようなToastを表示する仕組みがあったとします。
様々な箇所でAPIを呼び出す度、全てに同じ様な処理を書くのは効率が悪いです。
ここを上手く解決しましょう!Api.getUser().enqueue(object: Callback<Response?> { override fun onResponse(call: Call<Response?>, response: Response<Response?>) { if(!response.isSuccessful) { // エラー時の処理 Toast.makeText(context, "error!", Toast.LENGTH_SHORT).show() } } override fun onFailure(call: Call<Response?>, t: Throwable) { } })Callbackのオーバーライド
Callback
クラスを継承したクラスを作成し、onResponse
とonFailure
をオーバーライドすることで、共通の処理を追加することができます。open class CustomCallback<T>(private val context: Context): Callback<T> { override fun onResponse(call: Call<T>, response: Response<T>) { if(!response.isSuccessful) { // エラー時の共通処理 Toast.makeText(context, "error!", Toast.LENGTH_SHORT).show() } } override fun onFailure(call: Call<T>, t: Throwable) { } }この様なクラスを作る事により、すべてのAPI呼び出し処理に共通処理を追加可能です。
下記のように使用します。
Toastの処理は書いていませんが、super.onResponse(call, response)
で元の処理を呼び出しているので、Toastが表示されます。
もちろん、onFailure
の箇所にも自由に追加可能です。Api.getUser().enqueue(object: CustomCallback<Response?>(this) { override fun onResponse(call: Call<Response?>, response: Response<Response?>) { super.onResponse(call, response) } override fun onFailure(call: Call<Response?>, t: Throwable) { super.onFailure(call, t) } })簡単に共通処理を追加できるので、おすすめです!