- 投稿日:2020-03-30T23:33:01+09:00
ViewModelProviderクラスを定義せずにViewModelを生成する
はじめに
つい最近ViewModelを触り始めたのですが、当初は律儀にViewModelProvider(s)を使っていました。
しかし、fragment-ktxを使うことでかなり簡素にViewModelを生成出来たので自分用に纏めておきます。実装
jvmTargetを1.8に指定し、
lifecycle-viewmodel-ktx
とfragment-ktx
を追加する。build.gradleandroid { kotlinOptions { jvmTarget = '1.8' } } dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" implementation "androidx.fragment:fragment-ktx:1.2.3" }引数なしの場合
まずは引数なしのViewModelから。
とりあえず引数なしのViewModelを定義する。
initとhelloメソッド呼び出しのタイミングでそれぞれログを出力させる。MainViewModle.ktclass MainViewModel : ViewModel() { init { Log.d("MainViewModel", "init") } fun hello() { Log.d("MainViewModel", "hello") } }1行サクッと書くだけでViewModelが取得できる。
MainActivity.ktclass MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by viewModels() // ← ここ!! override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel.hello() } }initとhelloメソッド呼び出しのタイミングでそれぞれログが出力されているのがわかる。
log2020-03-30 22:48:02.199 3963-3963/com.masaibar.viewmodelsample D/MainViewModel: init 2020-03-30 22:48:02.199 3963-3963/com.masaibar.viewmodelsample D/MainViewModel: hello引数ありの場合
続いて引数ありの場合、先程のViewModelのコンストラクタを
userId
という引数を受け付けるように変更した。MainViewModle.ktclass MainViewModel( private val userId: String ) : ViewModel() { init { Log.d("MainViewModel", "init, userId: $userId") } fun hello() { Log.d("MainViewModel", "hello, userId: $userId") } }今度は先程のように1行だけサクッとという訳にはいかないが
それでもViewModelProviderクラスを定義しなくてもこのように書くことが出来る。MainActivity.ktclass MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by viewModels { object : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { @Suppress("UNCHECKED_CAST") return MainViewModel("masaibar") as T } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel.hello() } }initとhelloメソッド呼び出しのタイミングでそれぞれログが出力され
userId
が渡っていることが確認できる。log2020-03-30 22:58:52.525 4979-4979/? D/MainViewModel: init, userId: masaibar 2020-03-30 22:58:52.525 4979-4979/? D/MainViewModel: hello, userId: masaibarおわりに
Googleにはもう少しこれを推していって欲しいです。
- 投稿日:2020-03-30T14:11:31+09:00
【HelloAR】Unity 2019でARCore SDK for Unity v1.16.0 を実装する
はじめに
久々にARを実装してみようと思いました。Google公式 Quickstart for Androidを見れば簡単に作れますね。
環境
- Unity 2019.3.7f1
- ARCore SDK for Unity v1.16.0
- Google Pixel 3a
Unityの環境を確認し、必要なパッケージをダウンロードする
Unity 2019を使う場合は
Multiplayer HLAPI
とXR Legacy Input Helper
のパッケージが必要なのでダウウンロードしましょう。ARCore SDK for Unity v1.16.0をダウンロードする
こちらからダウンロードしてください。自動的にインポートされない場合は、ダウンロードしたUnityパッケージを
Assets
→Import Package
→Custom Package
で選択してインポートしてください。ビルド準備
今回実装するのはHelloARなのでダブルクリックしてシーンを合わせます。
File
→Build Settings
からAndroid
にSwitch Platform
しましょう。少し時間がかかります。終わったらScenes in Build
をHelloAR
にします。
Build Settings
の状態から左したのPlayer Settings
をクリックしてみましょう。以下のような画面が出てきます。以下の設定通りに変えましょう。
Vulkan
は必要ないので消しましょう。Package Name
は何でも大丈夫です。Minimum API Level
はAndroid 7.0
以上に設定しましょう。ARCore Supported
にはチェックを入れましょう。
ビルドしよう!
ビルドできるようにした状態のAndroidをしっかりUSBで接続しましょう。そして
Build And Run
で実行!ビルドに成功したら、自動的にアプリが立ち上がります。少し時間がかかりますが、その間は何も触れないようにしましょう。あれれ…ドロイド君じゃなくなった…!?
上手くビルドできない場合はAndroidの
開発者向けオプション
がon
になっているか。USBデバッグ
がon
になっているか。これらを確認しましょう。詳しくは参考文献を見てください。参考文献
- 投稿日:2020-03-30T09:56:11+09:00
Activity内に定義したAsyncTaskのネストクラスに警告「This 'AsyncTask' class should be static or leaks might occur」
この記事は、
「非staticネストクラスが握っちゃう、エンクロージングオブジェクトの暗黙的参照」と題した投稿の続きです。
ですので、先にそっちをご覧いただければ幸いです。
そっちの記事は、頭ごなし的に言っちゃえば、「ネストクラスには、static
修飾子を付けろ」です。いきなりカウントアップしだすアプリを作ってみた、ら...
起動したとたんに、勝手に1秒刻みでカウントアップが始まります。
START!から始まったら「9」まで数え上げたらお終いです。
(このアニメーションGIFは、3まで至ったらまたSTART!を繰り返しているアニメーションですが、このQiitaに貼り付けたアニメーションGIFを止めるすべがないのでごめんなさい)環境は以下の通りです。
Android Studio 3.6.1
Build #AI-192.7142.36.36.6241897, built on February 27, 2020
Runtime version: 1.8.0_212-release-1586-b04 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Windows 10 10.0
GC: ParNew, ConcurrentMarkSweep
Memory: 1237M
Cores: 8
Registry: ide.new.welcome.screen.force=true
Non-Bundled Plugins:Kotlinで開発しません。Javaでやります。
Activityのコード上に警告が出ます
このアプリのActivityは以下の通りです。
MainActivity.javapublic class MainActivity extends AppCompatActivity { private TextView textView; private MyTask task; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.text_view); task = new MyTask(); task.execute(); } @Override protected void onDestroy() { super.onDestroy(); task.cancel(true); } class MyTask extends AsyncTask<Void, String, Void> { @Override protected Void doInBackground(Void... voids) { for (int i = 0; i < 10; i++) { if (isCancelled()) { return null; // break;でもいいかも } try { Thread.sleep(1000); publishProgress(String.valueOf(i)); } catch (InterruptedException e) { Log.e("MyTask", e.getMessage(), e); } } return null; } @Override protected void onProgressUpdate(String... values) { textView.setText(values[0]); } } }そしたらAndroid Studioがこんな警告を出すんです。
This 'AsyncTask' class should be static or leaks might occur
MyTask
と名付けたネストクラスに、static
修飾子が付いていないことを咎めています。この警告を出さないようにする(した)のが、この記事の目的です。
これがベストな(主流の)解決方法のようです
まず先に、結論のコードを掲載します。
エンクロージングクラスであるMainActicity
オブジェクトの弱い参照を、staticネストクラスのオブジェクトは保持することにします。ということで、java.lang.ref.WeakReference
を使います。最善を尽くした修正public class MainActivityGood extends AppCompatActivity { // TextViewのフィールドはやめます。 private MyTask task; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.text_view); // this渡してる?なんで?それはね、MyTaskにコンストラクタを設けたからさ。 task = new MyTask(this); task.execute(textView); } @Override protected void onDestroy() { super.onDestroy(); task.cancel(true); } static class MyTask extends AsyncTask<Void, String, Void> { WeakReference<Activity> activityReference; // ここ注目! // コンストラクタを設けました。 public MyTask(Activity activity) { // 弱い参照でエンクロージングクラスのオブジェクトを保持することにします。 activityReference= new WeakReference<>(activity); } @Override protected Void doInBackground(Void... voids) { for (int i = 0; i < 10; i++) { if (isCancelled()) { return null; // break;でもいいかも } try { Thread.sleep(1000); publishProgress(String.valueOf(i)); } catch (InterruptedException e) { Log.e("MyTask", e.getMessage(), e); } } return null; } @Override protected void onProgressUpdate(String... values) { // エンクロージングクラスのオブジェクトの弱い参照は、getメソッドで取得できます。 Activity activity = activityReference.get(); // そして、その取得した弱い参照がnullになっちゃってないかのチェックはしておきましょう。 if (activity == null || activity.isFinishing()) { return; // ※ } TextView textView = activity.findViewById(R.id.text_view); textView.setText(values[0]); } } }結論はこうなんですが、せっかくなので途中私がこの結論に至るまでの逡巡を挟みつつ、最後には大団円を迎えたいと思います。
ネストクラスにstatic修飾子を付ける
Android Studioの警告の仰せの通りに、盲目的に(笑)
MyTask
と名付けたAsyncTask
サブクラスにstatic
修飾子を付けました。ただし、それだけの措置を施しただけですと、エンクロージングクラスの非staticフィールドをアクセスする際に以下の画像のように怒られます。なぜなのか。それは、 staticネストクラスは、エンクロージングのstaticメンバーにのみアクセス可能 だからです。
だったら、
MainActivityをこう修正すればイイじゃない?という安易な気持ちでpublic class MainActivity extends AppCompatActivity { private static TextView textView; // 以下略 }と修正するのは、否、それはチョット違うんでないかい?と気が咎めるのです。
我ながらひどいコードを書いたもんだ
わざとらしいのですが、まあ見てやってください。でも、こんなんで、Android Studioの例の警告は消えます。
MainActivityDasai.javapublic class MainActivityDasai extends AppCompatActivity { private MyTask task; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView textView = findViewById(R.id.text_view); task = new MyTask(); task.execute(textView); } @Override protected void onDestroy() { super.onDestroy(); task.cancel(true); } static class MyTask extends AsyncTask<TextView, Object, Void> { @Override protected Void doInBackground(TextView... textViews) { for (int i = 0; i < 100000; i++) { if (isCancelled()) { return null; // break;でもいいかも } try { Thread.sleep(1000); publishProgress(textViews[0], String.valueOf(i)); } catch (InterruptedException e) { Log.e("MyTask", e.getMessage(), e); } } return null; } @Override protected void onProgressUpdate(Object... values) { ((TextView)values[0]).setText((String)values[1]); } } }私の逡巡が、我が身と心を迷走させております。
- この
MyTask
は、このMainActivity
でしか使われないクラスだから、ネストクラスにしたい。- しかしstaticネストクラスにせよとIDEが警告を出す。
- ところが
TextTview
型のフィールドはstaticフィールドにはしたくない。- となると、この
MyTask
とこのMainActivity
は、別クラスに分けるか。でもそんなことをしたら、今はこのTextTview
1個っきりだからまあいいけど、他にもいろんなViewを複数扱うということになると、参照の受け渡しが面倒だ。- そしたらやっぱりネストクラスにするか...。
この私の逡巡が、「 では、
TextView
をエンクロージングクラスのフィールドにすること自体をやめよう! 」という思いに至って、このようなダサいプログラムになりました。而して、このダサいプログラムを書いた自分自身を咎めたくなりました。
どこが気にくわないかと言うと、ジェネリクスにObject
を指定する、というところに嫌気がさします。
おかげで、「((TextView)values[0]).setText((String)values[1]);
」の部分なんて、キャストしまくり、配列のインデックスどっちがどっち?と惑わせます。
加えて、doInBackground
メソッドの引数そしてAsyncTask
の第1ジェネリクスをTextView
にしたことも気にくわないです。TextView
以外のViewが増えたらどうするんだよ、と。WeakReferenceを使って、もらったActivityを"弱い参照"で扱う
java.lang.ref.WeakReference
を使って 弱い参照 として扱うことにしました。前掲のMainActivityGood.javaをご覧ください。ポイントは、
- staticネストクラスにした
MyTask
にコンストラクタを定義する。- エンクロージングクラスをジェネリックスで指定した
WeakReference
型フィールドも定義する。- そしてそれをコンストラクタの引数を使って
new
する。これにてMainActivityGood
の弱い参照を保持!- あとは必要に応じて、
WeakReference
からMainActivityGood
をゲット(メソッド名もget()
)して使う。前出のダサいやり方と違って、これなら
TextView
以外のViewを扱うことになっても対応が簡易になりますし、ジェネリクスにObject
を指定するという愚なこともせずにすみます。以上です。
参考
Qiitaのこの記事「This Handler class should be static or leaks might occur」に近い話です。
AsyncTaskはAPI レベル Rから非推奨になりました
android.os.AsyncTask
は、API レベル R1から非推奨になります。
この記事も、廃れる運命にあるわけです...。代替策は、
- java.util.concurrentパッケージをスタンダードに使うか、
- Kotlin concurrency utilitiesを使ってね。
とのことです。後者は、要はKotlinのCoroutinesのことです。
APIレベルは数字で示されるものなのに、この記事執筆時点では「R」となってます。 ↩
- 投稿日:2020-03-30T04:01:37+09:00
Theme.AppCompat.Light.NoActionBar を使っているアプリの Toolbar のテキスト、アイコンの色を白くする。
こんばんは。普段業務と個人で Android をゴリゴリ書いています。
今回、Toolbar の icon を白くしようとして少しハマったのでその備忘録です。
かなり限定的な場面で使用するので需要はそんなにないかと思いますが、同じ場面ではまった方の力になれば幸いです。アプリの設定
AndroidManifest.xml<application android:allowBackup="false" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>style.xml<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style>上記のコードを見ていただければわかるのですが、ActionBar 無しのテーマを全体に適用し、構成しています。
使用しているのはTheme.AppCompat.Light.NoActionBar
ですので、文字の色とアイコンの色は黒くなってしまいます。結論
結論ですが、
Toolbar
に別で作成したToolbar
用の スタイルをandroid:theme
に設定してあげます。style.xml... <!-- Toolbar のタイトルを白くする --> <style name="Toolbar.Light.Text" parent="ThemeOverlay.AppCompat.Dark.ActionBar"> <item name="android:background">@color/colorPrimary</item> </style>activity_main.xml<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- style ではなく theme に設定します --> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/Toolbar.Light.Text" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>ポイントは
android:style
ではなくandroid:theme
にstyle.xml
に作成したスタイルを追加するところです。
android:style
に設定した場合は指定したView
(今回はViewGroup
)にだけ作用します。
一方、android:theme
に設定した場合は、特にViewGroup
の場合は、その子ビューに対しても作用します。これでテキストとアイコンは白くなります。
最後に
なんだかAndroidはアプリ内リソースを簡単に管理できるところは良いのですが、ちょっと複雑だなぁと改め思いました。
見ていただきありがとうございます。