Android ViewModel RecyclerView

I changed the code from Android-Data-Binding-Starter-Example

Separate class, Use ViewModel class Use latest library (2020/04)

This sample covers following topics

  • View Binding
  • Data Binding
  • Retrofit 2
  • ViewModel
  • Recycler View Data Binding

build.gradle

We need dataBiding <- Data Binding and compatibility <- Retrofit

apply plugin: 'kotlin-android-extensions'

android {
   dataBinding {
        enabled = true
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'com.squareup.retrofit2:retrofit:2.8.1'
    implementation 'com.squareup.retrofit2:converter-moshi:2.8.1'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
}

Retrofit API

Get data from Github API

GithubApi.kt

object GithubApi {

    val client: GitHubService = Retrofit.Builder()
        .baseUrl("https://api.github.com")
        .addConverterFactory(MoshiConverterFactory.create())
        .build()
        .create(GitHubService::class.java)
}

GithubService.kt

interface GitHubService {
    @GET("search/repositories")
    fun searchRepositories(@Query("q") query: String) : Call<RepositoriesResponse>
}

Data

Owner.kt

data class Owner(
    val login: String,
    val avatar_url: String,
    val url: String
)

RepositoriesResponse.kt

data class RepositoriesResponse(
    val items: List<RepositoryItem>
)

RepositoryItem.kt

data class RepositoryItem(val name: String,
                          val full_name: String,
                          val owner: Owner,
                          val stargazers_count: Int,
                          val watchers_count: Int,
                          val forks_count: Int,
                          val languages : String
)

ViewModel

MainViewModel.kt

class MainViewModel : ViewModel() {

    val searchWord = ObservableField("")

    val loading = ObservableBoolean()

    val repositories = MutableLiveData<List<RepositoryItem>>()

    fun search(word: String) {
        loading.set(true)

        repositories.postValue(emptyList())

        GithubApi.client.searchRepositories(word).enqueue(object: Callback<RepositoriesResponse> {
            override fun onFailure(call: Call<RepositoriesResponse>, t: Throwable) {
                loading.set(false)
                Log.e("Error", "Why?", t)
            }

            override fun onResponse(
                call: Call<RepositoriesResponse>,
                response: Response<RepositoriesResponse>
            ) {
                loading.set(false)
                if (response.isSuccessful) {
                    response.body().let {
                        repositories.postValue(it?.items)
                    }
                }
            }
        })
    }
}

Layout

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <import type="android.view.View"/>
        <variable
            name="viewModel"
            type="com.daiji110.bindgsampleapp.viewmodel.MainViewModel" />
    </data>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <EditText
            android:id="@+id/search_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_toStartOf="@id/button_search"
            android:text="@={viewModel.searchWord}"
            />
        <Button
            android:id="@+id/button_search"
            style="@style/Widget.AppCompat.Button.Borderless"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:onClick="@{(v) -> viewModel.search(viewModel.searchWord)}"
            android:text="Search"/>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@id/button_search"
            tools:context=".MainActivity"/>
        <ProgressBar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true"
            android:visibility="@{viewModel.loading ? View.VISIBLE : View.GONE}"/>

    </RelativeLayout>
</layout>

list_repository.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="item"
            type="com.daiji110.bindgsampleapp.data.RepositoryItem" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp">

        <TextView
            android:id="@+id/title_text_view"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@{item.full_name}"
            android:textStyle="bold"
            tools:text="Title"/>
        <TextView
            android:id="@+id/due_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(item.stargazers_count)}"
            tools:text="100"/>
    </LinearLayout>
</layout>

MainActivity

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private val viewModel = MainViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.viewModel = viewModel

        val adapter = RepositoryAdapter(this)
        binding.recyclerView.layoutManager = LinearLayoutManager(this)
        binding.recyclerView.adapter = adapter

        viewModel.repositories.observe(this, Observer { response ->
            response?.let {
                adapter.items = it
            }
        })
        binding.lifecycleOwner = this
    }

    class RepositoryAdapter(context: Context) : RecyclerView.Adapter<Holder>() {
        private val inflater = LayoutInflater.from(context)
        var items: List<RepositoryItem> = emptyList()
            set(value) {
                field = value
                notifyDataSetChanged()
            }

        override fun getItemCount(): Int = items.size

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
            val binding: ListRepositoryBinding = DataBindingUtil.inflate(inflater, R.layout.list_repository, parent, false)
            return Holder(binding)
        }

        override fun onBindViewHolder(holder: Holder, position: Int) {
            holder.binding.item = items[position]
            holder.binding.executePendingBindings()
        }
    }

    class Holder(val binding: ListRepositoryBinding) : RecyclerView.ViewHolder(binding.root)
}
Android
スポンサーリンク
Professional Programmer2

コメント