LoginSignup
2
5

More than 3 years have passed since last update.

Android2015年知識の私が2020年のAndroid開発事情に追いつく

Last updated at Posted at 2020-09-26

https://qiita.com/hikaruna/items/d0cd8f4d82aa03302689
の記事を書いた頃である5年前から今までAndroidから離れていましたが、また触る機会を得ました。Androidのことだからどうせ原型を留めないほどに変わっているだろうなと覚悟しながら望むつもりです。

kotlin

kotlinを使えという世界になったらしい。
javaを選択することはできますし、ファイル単位で共存ができますが、今更新たにjavaを書くべきではない、kotlinから逃げてはいけないと、私は理解しました。

大体javaをswiftっぽくかけるだけだろうと舐めて掛かったのですが、以下が読めなくて無事死亡しました。

  • primary constructor
  • combine object
  • by

このあたりは、周辺言語の経験で雰囲気を掴む感覚が働かなかったのでおとなしく公式ドキュメント読みました。

コルーチンまわりは難しすぎてまだよくわかっていません。パット見iOSと似てきた気がします。

jetpack

よくわからないですがサポートライブラリのセットらしいです。

androidx

従来のサポートライブラリを置き換える、すごいサポートライブラリっぽい。androidx ∋ jetpack
従来とは違ってSDKのバージョンとは完全に無関係に、ライブラリのバージョンが管理されている。
なので、とりあえずあらゆる実装はなるべくandroidxパッケージのものを使うべきだと、私は解釈した。何も考えたくない。activityしかりfragmentしかり。何もかもすべて。

KTX

androidxライブラリが提供している、androidのAPIをkotlinで使いやすくするための、拡張されたandroidAPI。
パッケージ名に規則があって androidx.xxx.xxx.fragment → androidx.xxx.xxx.fragment-ktx となっている。
正直本当にリーダブルか疑わしいので、上方互換性が心配だけれども、細かいことを抜きにすれば、(-ktx)があるパッケージならとりあえずそれ使っておけばいいと、私は解釈した。

ADV

めっっっっっっっっちゃ早くなっているので、公式のADV使っておけばOKになった。
google playと書いてあるイメージは、non rootでgoogle play storeがプリインストールされていて、実際の商品のデバイスに近いイメージ。ソシャゲとかもできるものは普通できる。
google apiと書いてあるのはrootでgoogle play storeは入っていないらしい(使う理由がないので使っていない)

アーキテクチャ

https://developer.android.com/jetpack/guide?hl=ja
MVVMとかfluxとかアレ系の波がandroidにも来ている。iOSと違って公式フレームワークとしてこういうデザインパターンを強制するものをガシガシ出すのがAndroidっぽい。
こういうのを使わなくてもアプリは作れるけど、ある程度複雑な画面があると、こういうのを使ったほうが良いのは経験上知っているので、逃げずに使おうと思いました。

サポートライブラリとは

新しいバージョンのSDK?android-api?で出た機能を古いバージョンでも動くようにするためのものです。従来はandroid.supportというネームスペースで出てたそうな。appcompatとかもそれの一部だそうな。…多分。

Binding

findViewByIdの置き換え。
簡単に使えるViewBindingと複雑なDataBindingがある

ViewBinding

参考: https://developer.android.com/topic/libraries/view-binding?hl=ja
本当に、単なるfindViewByIdの置き換え。
単純により使いやすくより安全になった。

layout/xxx.xmlと同じ名前の(ex: XxxBinding)クラスが自動生成されソレを通してViewにアクセスする

app/build.gradle
android {
  ...
  binding {
    enabled = true
  }
}
// res/layouts/xxx.xml
<FrameLayout>
    <TextView
      android:id="+@id/my_text_view"
    />
</FrameLayout>
val binding = XxxBinding(infrater, parent, atachToRoot = false)
val myTextView: TextView = binding.myTextView
myTextView.text = "text"

DataBinding

ViewBindingから一歩進んで、xml側で変数宣言してどのViewのどのAttributeにどう使うかを宣言する。

app/build.gradle
android {
  ...
  binding {
    enabled = true
  }
}
// res/layouts/xxx.xml
<layout>
    <data>
        <variable
            name="text"
            type="String"
    </data>
    <FrameLayout>
        <TextView
          android:id="+@id/my_text_view"
          android:text="@{text}"
        />
    </FrameLayout>
</layout>
val binding = XxxBinding(infrater, parent, atachToRoot = false)
val myTextView: TextView = binding.myTextView
myTextView.text = "text"

DataBinding LiveData 双方向

こんな感じでできる

// res/layouts/xxx.xml
<layout>
    <data>
        <variable
            name="text"
            type="androidx.lifecycle.MutableLiveData&lt;String&gt;"
    </data>
    <FrameLayout>
        <EditText
          android:id="+@id/my_text_view"
          android:text="@={text}"
        />
    </FrameLayout>
</layout>

あとはxmlでクロージャーも書けてしまうがほどほどに

ToolBar (旧 ActionBar)

ActionBarはオワコン。置き換えるべし。
ただし使い方が難しい。

ToolBarの背景及びTextColorを変える方法する方法

toolbar thema text background color change custom

// res/layouts/xxx.xml
    <androidx.appcompat.widget.ToolBar
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:thema="@style/AppTheme.AppBar"
    />

// res/values/styles.xml
    <style name="AppTheme" parent="ThemeAppCompat.{NoActionBar系}">
        <item name="colorPrimary">@color/colorPrimary</item><!-- ← layoutにて、 ?attr/colorPrimaryで参照される -->
    </style>
    <style name="AppTheme.AppBar"><!-- ← parentを書かずにピリオドで書いているのでAppThemeを継承している -->
        <item name="android:textColorPrimary">@android:color/white</item><!-- ToolBarが自動的に参照しているようで、これでToolBarのTitleColor及びMenuItemのtextColorが変わった -->
    </style>

LegacyViewと今どきのView

  • ListView -> RecyclerView
  • TabHost -> TabLayout
  • RelativeLayout -> ConstraintLayout
  • GridView -> ConstraintLayout
  • ほかにもいろいろ

ConstraintLayout

android-studioのGUIエディタがある程度サポートしてくれるRelativeLayoutみたいな雰囲気。
だいたいは https://qiita.com/tktktks10/items/62d85dabac4bdb8c1f94 を読んで使っている。
安易にLinearLayoutをネストして作るのではなく、こういう無敵のLayoutを用いてできるだけLayoutのnestは避けるべし。
ややこしい。RelativeLayoutと作り分ける理由がわからん。細かくAPI違う。
GridViewの代替と言っているが、動的なコンテンツに対応できるものなのか疑問(調査中)


以下詰まったことを無限にメモります

Fragment#onActivityCreated(Bundle?): Unit is deprecated

ボイラープレートや、アーキテクチャガイドでも普通に使ってるのに言われる。どうすれば良いのかIDEは教えてくれない。
ググる
https://developer.android.com/reference/androidx/fragment/app/Fragment#onActivityCreated(android.os.Bundle)

どうやらこのコールバックを使うこと自体が駄目らしい。onViewCreatedやonCreated使っとけとのこと。

で、地雷があって、ぐぐるとネイティブのFragmentのリファレンスやら昔のサポートライブラリのFragmentのリファレンスが引っかかる。しっかりとandroidxのFragmentのAPIリファレンスであることを確認するべし。

viewModels() 使ったら、 Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option

アーキテクチャガイドに従い

class MainFragment : Fragment() {
    private val viewModel: MainViewModel by viewModels()

と書いたら`viewModelsの出ました。

を参考にして

appのbuild.gralde
android {
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
}

で解決。
もうこの時点でAndroidのガイドの品質がかなり絶望的になっている。

android-studioがtooltipで出すエラーメッセージコピペできない

この記事を書くにあたって詰まった。
https://stackoverflow.com/a/43665444/13639556
によると、なんか…こうShiftキーをうまく押しながらやればコピーできるらしい。できた。
Macだとcmdキーをうまく押せばできた。

gradle dependencyのversion指定

  • latest.release
    • stateがrelaseなものの最新
  • 1.*
    • 1系の最新
  • +
    • 最新

inflateLayoutで include must has layout的なランタイムエラー

ちゃんとlayout属性していていて、GUIエディタのプレビューには出てるのに!

対処法

layouts.xml
- <include android:layout="..." />
+ <include layout="..." />

ひどすぎる。

Android11(api-30)にアップデートしたらSpeechRecognizer bind failed

Pixel3で発生。
原因は不明。
推測だが、googleさんによる機能の削ぎ落とし。次点でバグ。
機能の削ぎ落としについて、googleさんは意外とやってきやがるんで何も信用できない。オフライン日本語の音声認識がそう。既に前例がある。

こんなのを使ってしまったら後悔するので、有料のサードパーティのものへシフトのが今の時代では良いと思う。

私は検証端末のダウングレードと最新バージョンをサポートしないことを選びました。

コンストラクタ引数のあるviewModelのviewModels()をつかった生成

class MyViewModel(val a: Int): ViewModel() {
  class Factory constructor(private val a: Int): ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            @Suppress("UNCHECKED_CAST")
            return MyViewModel(this.a) as T
    }
  }
}

class MyActivity: AppCompatActivity() {
  // viewModels is fragment-ktx package
  val viewModel: MyViewModel by viewModels { MyViewModel.Factory(a = 1) }
}

よくあるユースケースなのに、ベーシックなチュートリアルにはなかなか書いてないんだわ。

コンストラクタ引数のあるviewModelの viewModels() をつかった生成 とSavedStateViewModelFactryを組み合わせた場合

https://proandroiddev.com/saving-ui-state-with-viewmodel-savedstate-and-dagger-f77bcaeb8b08
を参考にさせていただきました

class MyViewModel(
  val a: Int,
  savedStateHandle: SavedStateHandle
): ViewModel() {

  class Factory(
    private val a: Int,
    owner: SavedStateRegistryOwner,
    defaultArgs: Bundle? = null
  ): AbstractSavedStateViewModelFactory(owner, defaultArgs) {
    override fun <T : ViewModel> create(
        key: String,
        modelClass: Class<T>,
        handle: SavedStateHandle
    ): T {
      return MyViewModel(a = this.a, savedStateHandle = handle) as T
    }
  }
}

class MyActivity: AppCompatActivity() {
  // viewModels is fragment-ktx package
  val viewModel: MyViewModel by viewModels { MyViewModel.Factory(a = 1, owner = this) }
}

コンストラクタ引数のあるviewModelの viewModels() をつかった生成 Fragment#activityViewModels() を使ったSharedViewModelの実現の組み合わせ

androidで、コンストラクタに引数があるVMを、ActivityとFragment間で共有する方法が参考になります。(というか私の質問と私の回答なのですが…)

要約すると viewModelsとacitivityViewModelの引数を与えるのをやめ、Activity#getDefaultViewModelProviderFactory()をoverrideします。

class MyViewModel(val a: Int): ViewModel() {
  class Factory constructor(private val a: Int): ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            @Suppress("UNCHECKED_CAST")
            return MyViewModel(this.a) as T
    }
  }
}

class MyActivity: AppCompatActivity() {
  // viewModels is fragment-ktx package
-  val viewModel: MyViewModel by viewModels { MyViewModel.Factory(a = 1) }
+  val viewModel: MyViewModel by viewModels()

+  override fun getDefaultViewModelProviderFactory(): ViewModelProvider.Factory {
+    return MyViewModel.Factory(a = 1)
+  }
}

class MyFragment: Fragment() {
    val viewModel: MyViewModel by activityViewModels() // 成功! そしてshareされている!

}
2
5
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5