Last active
May 8, 2022 23:21
-
-
Save Pooh3Mobi/219e7ae5f2bf35edd69129250785ce65 to your computer and use it in GitHub Desktop.
RecyclerView Sample in 2022 (for my memo)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
plugins { | |
id 'com.android.application' | |
id 'org.jetbrains.kotlin.android' | |
id 'kotlin-kapt' | |
id 'dagger.hilt.android.plugin' | |
} | |
android { | |
compileSdk 32 | |
defaultConfig { | |
applicationId "com.example.recyclerviewsample" | |
minSdk 21 | |
targetSdk 32 | |
versionCode 1 | |
versionName "1.0" | |
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | |
} | |
buildTypes { | |
release { | |
minifyEnabled false | |
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | |
} | |
} | |
compileOptions { | |
sourceCompatibility JavaVersion.VERSION_1_8 | |
targetCompatibility JavaVersion.VERSION_1_8 | |
} | |
kotlinOptions { | |
jvmTarget = '1.8' | |
} | |
} | |
dependencies { | |
implementation 'androidx.core:core-ktx:1.7.0' | |
implementation "androidx.fragment:fragment-ktx:1.4.1" | |
implementation 'androidx.appcompat:appcompat:1.4.1' | |
implementation 'com.google.android.material:material:1.6.0' | |
implementation 'androidx.constraintlayout:constraintlayout:2.1.3' | |
implementation "androidx.recyclerview:recyclerview:1.2.1" | |
// For control over item selection of both touch and mouse driven selection | |
// implementation "androidx.recyclerview:recyclerview-selection:1.1.0" | |
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0" | |
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.ext.versions.lifecycle" | |
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$rootProject.ext.versions.lifecycle" | |
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$rootProject.ext.versions.lifecycle" | |
implementation "androidx.lifecycle:lifecycle-common-java8:$rootProject.ext.versions.lifecycle" | |
// DI | |
implementation "com.google.dagger:hilt-android:$rootProject.ext.versions.hilt" | |
kapt "com.google.dagger:hilt-android-compiler:$rootProject.ext.versions.hilt" | |
// implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03" | |
kapt 'androidx.hilt:hilt-compiler:1.0.0' | |
testImplementation 'junit:junit:4.13.2' | |
androidTestImplementation 'androidx.test.ext:junit:1.1.3' | |
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Top-level build file where you can add configuration options common to all sub-projects/modules. | |
plugins { | |
id 'com.android.application' version '7.1.3' apply false | |
id 'com.android.library' version '7.1.3' apply false | |
id 'org.jetbrains.kotlin.android' version '1.6.0' apply false | |
id 'com.google.dagger.hilt.android' version '2.41' apply false | |
} | |
ext { | |
versions = [ | |
"lifecycle" : "2.4.1", | |
"hilt" : "2.41", | |
] | |
} | |
task clean(type: Delete) { | |
delete rootProject.buildDir | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.recyclerviewsample.ui | |
import android.os.Bundle | |
import android.view.View | |
import android.widget.Toast | |
import androidx.appcompat.app.AppCompatActivity | |
import androidx.fragment.app.Fragment | |
import androidx.fragment.app.viewModels | |
import androidx.lifecycle.Lifecycle | |
import androidx.lifecycle.lifecycleScope | |
import androidx.lifecycle.repeatOnLifecycle | |
import androidx.recyclerview.widget.RecyclerView | |
import com.example.recyclerviewsample.R | |
import dagger.hilt.android.AndroidEntryPoint | |
import kotlinx.coroutines.launch | |
@AndroidEntryPoint | |
class MainActivity : AppCompatActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.activity_main) | |
} | |
} | |
@AndroidEntryPoint | |
class MainFragment : Fragment(R.layout.fragment_main) { | |
private val viewModel: MainViewModel by viewModels() | |
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | |
val listView = view.findViewById<RecyclerView>(R.id.listView).apply { | |
adapter = MyAdapter() | |
} | |
viewLifecycleOwner.lifecycleScope.launch { | |
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { | |
viewModel.uiState.collect { uiState -> | |
listView.submitList(uiState.data) | |
uiState.userMessages.firstOrNull()?.let { userMessage -> | |
showToast(userMessage.message) | |
viewModel.userMessageShown(userMessage.id) | |
} | |
} | |
} | |
} | |
} | |
private fun showToast(message: String) = | |
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() | |
private fun RecyclerView.submitList(itemDataList: List<ItemDataItemUiState>) = | |
(this.adapter as MyAdapter).submitList(itemDataList) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.recyclerviewsample.ui | |
import androidx.lifecycle.ViewModel | |
import androidx.lifecycle.viewModelScope | |
import com.example.recyclerviewsample.data.ItemDataRepository | |
import com.example.recyclerviewsample.model.ItemData | |
import dagger.hilt.android.lifecycle.HiltViewModel | |
import kotlinx.coroutines.flow.MutableStateFlow | |
import kotlinx.coroutines.flow.StateFlow | |
import kotlinx.coroutines.flow.asStateFlow | |
import kotlinx.coroutines.flow.update | |
import kotlinx.coroutines.launch | |
import java.util.* | |
import javax.inject.Inject | |
data class UserMessage( | |
val id: Long, | |
val message: String | |
) | |
data class ItemDataItemUiState( | |
val id: String, | |
val title: String, | |
val onItemLongClick: () -> Unit, | |
val onItemClick: () -> Unit, | |
) | |
data class MainUiState( | |
val data: List<ItemDataItemUiState> = emptyList(), | |
val userMessages: List<UserMessage> = emptyList() | |
) { | |
fun updateData(newData: List<ItemDataItemUiState>) = | |
this.copy(data = newData) | |
fun addMessage(id: Long = UUID.randomUUID().mostSignificantBits, message: String) = | |
this.copy(userMessages = userMessages + UserMessage(id, message)) | |
fun removeMessage(id: Long) = | |
this.copy(userMessages = userMessages.filterNot { it.id == id }) | |
} | |
@HiltViewModel | |
class MainViewModel @Inject constructor( | |
private val itemDataRepository: ItemDataRepository, | |
) : ViewModel() { | |
private val _uiState = MutableStateFlow(MainUiState()) | |
val uiState: StateFlow<MainUiState> = _uiState.asStateFlow() | |
init { | |
viewModelScope.launch { | |
val data = itemDataRepository.getAll() | |
_uiState.update { currentUiState -> | |
currentUiState.updateData(data.map { it.toItemUiState() }) | |
} | |
} | |
} | |
private fun remove(itemData: ItemData) { | |
viewModelScope.launch { | |
itemDataRepository.remove(itemData) | |
val data = itemDataRepository.getAll() | |
_uiState.update { currentUiState -> | |
currentUiState.updateData(data.map { it.toItemUiState() }) | |
} | |
} | |
} | |
private fun showItemDataInfo(itemData: ItemData) { | |
_uiState.update { currentUiState -> | |
currentUiState.addMessage(message = "clicked item: $itemData") | |
} | |
} | |
fun userMessageShown(messageId: Long) { | |
_uiState.update { currentUiState -> | |
currentUiState.removeMessage(messageId) | |
} | |
} | |
private fun ItemData.toItemUiState(): ItemDataItemUiState { | |
return ItemDataItemUiState( | |
id = id, | |
title = name, | |
onItemLongClick = { [email protected](this) }, | |
onItemClick = { [email protected](this) } | |
) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.recyclerviewsample | |
import com.example.recyclerviewsample.data.ItemDataRepository | |
import dagger.Module | |
import dagger.Provides | |
import dagger.hilt.InstallIn | |
import dagger.hilt.components.SingletonComponent | |
import kotlinx.coroutines.CoroutineDispatcher | |
import kotlinx.coroutines.Dispatchers | |
import javax.inject.Qualifier | |
import javax.inject.Singleton | |
@Retention(AnnotationRetention.RUNTIME) | |
@Qualifier | |
annotation class IoDispatcher | |
@Module | |
@InstallIn(SingletonComponent::class) | |
class RepositoryModule { | |
@Singleton | |
@Provides | |
fun provideItemDataRepository( | |
@IoDispatcher coroutineDispatcher: CoroutineDispatcher, | |
): ItemDataRepository = ItemDataRepository(coroutineDispatcher) | |
} | |
@Module | |
@InstallIn(SingletonComponent::class) | |
class CoroutineDispatcherModule { | |
@IoDispatcher | |
@Provides | |
fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.recyclerviewsample.ui | |
import android.view.LayoutInflater | |
import android.view.ViewGroup | |
import android.widget.TextView | |
import androidx.recyclerview.widget.DiffUtil | |
import androidx.recyclerview.widget.ListAdapter | |
import androidx.recyclerview.widget.RecyclerView | |
import com.example.recyclerviewsample.R | |
private typealias ItemUiState = ItemDataItemUiState | |
class MyAdapter : ListAdapter<ItemDataItemUiState, RecyclerView.ViewHolder>(DIFF_CALLBACK) { | |
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { | |
val view = LayoutInflater.from(parent.context) | |
.inflate(R.layout.simple_text_item, parent, false) | |
return object : RecyclerView.ViewHolder(view) {} | |
} | |
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { | |
val itemUiState = getItem(position) | |
val view = holder.itemView.apply { | |
setOnClickListener { itemUiState.onItemClick() } | |
setOnLongClickListener { itemUiState.onItemLongClick(); true } | |
} | |
val textView = view.findViewById<TextView>(R.id.textView) | |
textView.text = itemUiState.title | |
} | |
} | |
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<ItemUiState>() { | |
override fun areItemsTheSame(old: ItemUiState, new: ItemUiState) = | |
old.id == new.id | |
override fun areContentsTheSame(old: ItemUiState, new: ItemUiState) = | |
old == new | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://developer.android.com/topic/architecture/ui-layer/events#recyclerview-events
RecyclerViewのAdapterにViewModelは渡さないこと。AdapterとViewModelが密結合してしまうバッドプラクティスです。