Android GlideをDataBindingで使うサンプルコード(Kotlin)

Glideは、Androidアプリで画像を表示するときに、簡単にセットしてくれたり、丸く切り抜いてくれる便利なライブラリです。

今回、そのGlideをDatabindingと一緒に使う方法です。

最初、下記を見てやってましたが

https://androidwave.com/loading-images-using-data-binding/

うまくいかなかったので、自分でちょっとアレンジしています。

GlideとDataBinding自体の適用は、いろんなところで情報がありますので、割愛させていただきます。

①まず、Fragmentのレイアウトファイル。DataBinding時代になってから、レイアウトファイルをいかにうまく書くか、は結構難しくなりましたね。

fragment.user_list.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="imageUrl"
            type="String" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">


        <ImageView
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:id="@+id/ivProfileImage"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_gravity="center"
            android:layout_margin="5dip"
            android:contentDescription=""
            app:profileImage="@{imageUrl}" />

    </androidx.constraintlayout.widget.ConstraintLayout>


</layout>

UsrListViewModel.kt ビューモデルです。

class UserListViewModel constructor(private val repository: UserRepository) : ViewModel() {

    val TAG: String = "UserListViewModel"
    //監視対象のLiveData
    var userListLiveData = MutableLiveData<List<User>>()



    //引数が必要な時は、Factoryが必要
    class Factory(private val repository: UserRepository) :
        ViewModelProvider.NewInstanceFactory() {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return UserListViewModel(repository) as T
        }
    }

  //これが大事 スタティックにしないとエラーが出ます。
    companion object {

        @JvmStatic
        @BindingAdapter("profileImage")
        fun loadImage(view: ImageView, imageUrl: String?) {

       //imageUrlはnullチェックが必要です。
            if(imageUrl != null) {
                Glide.with(view.context)
                    .load(imageUrl).apply(RequestOptions().circleCrop())
                    .into(view)
            }
        }
    }

}

なんで、これスタティックにしないといけないのかなーとか、思ってましたが、スタティックにしないと次のエラーが出ます。

java.lang.IllegalStateException: Required DataBindingComponent is null in class FragmentUserListBindingImpl. A BindingAdapter in jp.onlineconsultant.hogehoge.User is not static and requires an object to use, retrieved from the DataBindingComponent. If you don't use an inflation method taking a DataBindingComponent, use DataBindingUtil.setDefaultComponent or make all BindingAdapter methods static.

imageUrlをnull許可にして、nullチェックしないと、次のエラーも出ます。

Process: jp.onlineconsultant.hogehoge.dev, PID: 19146
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter imageUrl

③Fragmentです。値をセットします。


UserListFragment.kt

class UserListFragment : Fragment() {

    private lateinit var userListViewModel: UserListViewModel
    private lateinit var binding: FragmentUserListBinding


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        val database = getDatabase(getActivity() as MainActivity)
        val repository = UserRepository(database.userDao)

        userListViewModel = ViewModelProviders.of(this, UserListViewModel.Factory(repository))
            .get(UserListViewModel::class.java)


        //dataBinding用のレイアウトリソース
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_user_list, container, false)


        setRecycleView(binding)

        return binding.root
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        binding.setImageUrl("https://pbs.twimg.com/profile_images/1162746270697443336/EMUCXT0E_400x400.jpg");
        observeViewModel(userListViewModel)
    }

  

}

Property must be initialized or be abstract

まだまだ全然Kotlin初心者の私です…。

たとえばなんですけど、次のようにメンバー変数を定義したい時に

@Parcelize
class User(val id: Int, val name: String) :Parcelable {

    val reading:String //ここで冒頭のエラー
  val age:Int //ここで冒頭のエラー

}

ってなりますよね。

var reading:String? = null

ってすれば直るんですが。

またはコンストラクタにreading とかageとか入れてもいいです。しかし、呼び出すときに全部入れなきゃいけないの??って思ってました。

これ、

init{}

でやれば、定義の時点でvalでもいけるのに後で気づきました。(;^ω^)

次のような感じです。

@Parcelize
class User(val id: Int, val name: String) :Parcelable {

    val reading:String

    init{
        reading = "hogehoge"
    }
}

Didn’t find class “android.support.v7.widget.RecyclerView” on path: DexPathLis

RecycleViewを使おうとして、上記のエラーに遭遇しました。

実機で実行すると、レイアウトファイルのinflate時にこのエラーでクラッシュします。

数時間ぐらい悩んだんですよねー。

多くのサイトに

import android.support.v7.widget.RecyclerView

import androidx.recyclerview.widget.RecyclerView

にすべしというのが書いてありますがっ。それはやってあるんだよなー。(´ω`)

下記のサイトに答えがありました。

https://tutorialmore.com/questions-296241.htm

レイアウトファイルのRecycleViewの指定を下記から

    <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
             />

次に変更します。

    <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
             />

これで、クラッシュしなくなりました!

P.S こういう自動翻訳サイトって読みづらいので避けていたんですが、最近は、こういうので助かるケースも増えてきました。

MainActivityBinding not found

DataBindingを使う時に下記のように書いたんですけど

//MainActivity.kt内
class MainActivity : AppCompatActivity() {

    private lateinit var model: TaskViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        var binding:ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.content_main)
  }
}

まずはDataBindingUtilが見つからない。

これは、build.gradleに下記を書いてないからでした。

android{ 
dataBinding{
enabled = true
}
}

これねー、Room+LiveData+ViewModelでやるときは、この

dataBinding enabled =true

いらないんですよ。なので、ちょっとした落とし穴ですね。

んで、本題の

MainActivityBinding not found

ですが、この一文の中にすでに答えがあります。

var binding:ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.content_main)

Bindingの型はレイアウトファイルの名称に依存するのです。

つまり、この場合は、レイアウトファイルがcontent_mainという名前なので、

var binding:ContentMainBinding =
            DataBindingUtil.setContentView(this, R.layout.content_main)

ってすると、動きます。


Android Parcelableなクラスのメンバ変数に独自オブジェクトを入れる(Kotlin サンプルコード)

例えば、TaskというクラスとUserというクラスがあります。

Taskのメンバ変数に、Userクラスを指定したいものがあります。

//Task.kt内
@Parcelize
class Task(val id: String, var content: String?,  val to:User, var limit:String?):Parcelable{
    //中略
}
//User.kt内
class
User(val id: String, val name: String){
    //中略
}

ちな、Kotlinのどっかのバージョンから、Parcelableにしたいクラスを超簡単にかけるようになりました!!

以前は、定義しなければならないメソッドなどがいっぱいあって、クラスをParcelableにするのに一手間かかってました。

それが、 Task.ktであるように、

@Parcelable

というアノテーションと、

:Parcelable

で継承するだけでOkになりました。時代が進むっていいね(´ω`)

さて、本題に戻り、上記のTaskとUserでは、下記のエラーが出てこのままではビルドできません。

Type is not directly supported by ‘Parcelize’. Annotate the parameter type with ‘@RawValue’ if you want it to be serialized using ‘writeValue()’

えー。

これ、Userの方もParcelableにすれば直ります。

なので、User.ktを次のようにします。

@Parcelize
class
User(val id: String, val name: String) :Parcelable {
    //中略
}

Javaでもこうでしたっけ? 昨日、I forget.


Parcelは小包みって意味らしいです。