Generic list sayfaları yapalım

Proje geliştirirken edindiğim best practice varsa o da benzer işlevlere sahip sayfaları dinamik olarak kurgulamaktır. Projelerde her zaman generic bir yapı kurabiliriz. Bu başta bize zaman kaybı gibi gelse de elimizde generic kurguladığımız güzel bir kütüphane olduğunda sonraki projelerin hızla ortaya çıkması gibi meyveler verebiliyor. Ki bu da tadından yenmez.
Projemizde kullanacağımız ViewModellar için common bir BaseViewModel oluşturarak override edebileceğimiz ortak metodları yazabiliriz.
abstract class BaseViewModel : ViewModel(){var liveDataResult: MutableLiveData<Any> = MutableLiveData()
var request = BaseRequest()abstract fun getResults()}
Şöyle bir ViewModel ve Repository oluşturalım.
Repository :
class ProductRepository() : BaseRepository()
{suspend fun getProductResults(request: ProductRequest) : MutableLiveData<Any>
{
val liveDataResult = MutableLiveData<Any>()
try {
val response = jsonApi?.getProducts(request.productId)
when{
response?.result!! -> {
liveDataResult.postValue(response.data)
}
}
}catch (exception : Exception){
Log.e("ERROR", exception.message.toString())
}
return liveDataResult
}
}
ViewModel :
class ProductViewModel : BaseViewModel()
{
private var repository = ProductRepository()
override fun getResults() {viewModelScope.launch {
liveData = repository.getProductResults(request as ProductRequest)
}
}
}
Burada getResults metodunu BaseViewModel’dan base alıyorum. Bunu yapmamın nedeni fragment içerisinde kullandığım viewModel’in tipine bakmaksızın (yani viewModeli kullandığım heryerde casting yapmadan) benzer işlevlere sahip olan fragmentlarda generic bir yapı oluşturarak gelen viewModele göre metodları aynı isimle çağırmak. Böylece dinamik bir fragment oluşturmak. Daha açık anlatmak gerekirse, salt listeleme yapan bir sayfa için hangi fragmentda olduğum farketmeksizin aşağıdaki işlemi yaparak dinamik sayfalar oluşturabilirim.
viewModel.getResults()
viewModel.liveDataResult.observe(
viewLifecycleOwner,
Observer { result ->
// Result listesini RecyclerView'de göster.})

Şimdi Generic bir adapter de iyi gider :
class GeneralAdapter(private var mList: List<BaseInterface?>): RecyclerView.Adapter<BaseViewHolder<*>?>() {
fun setItems(_mList: List<BaseInterface?>)
{
mList = _mList
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> {
var inflater : LayoutInflater = LayoutInflater.from(parent.context)
return when (viewType) {
ModelType.PRODUCTS -> ProductsHolder(
inflater.inflate(
R.layout.item_layout_product,
parent,
false
)
)
ModelType.BLABLA -> BlaBlaHolder(
inflater.inflate(
R.layout.item_layout_blabla,
parent,
false
)
)
else -> MessagesHolder(
inflater.inflate(
R.layout.item_layout_message,
parent,
false
)
)
}
}
override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
holder.bind(mList[position])
}
class ProductsHolder(itemView: View) : BaseViewHolder<Message?>(itemView) {
val txtProductName: TextView = itemView.findViewById<View>(R.id.txtProductName) as TextView
val txtProductPrice: TextView = itemView.findViewById<View>(R.id.txtProductPrice) as TextView
override fun bind(item: BaseInterface?) {
var product = (item as Product)
txtProductName.text = product.productName
txtProductPrice.text = product.productPrice
itemView.setOnClickListener {
// TO DO
}
}
}
override fun getItemViewType(position: Int): Int {
return mList[position]!!.viewType
}
override fun getItemCount(): Int {
return mList.size
}
}
BaseInterface viewType ile kullanacağımız modelin tipini tutacak. BaseViewModel BaseInterfaceden implement edilmeli. Ki onCreateViewHolder fonksiyonu içerisinde elimizdeki listenin içerisindeki objenin viewTypeıne göre layout ve holder tipini belirleyebilelim.
abstract class BaseViewHolder<T>(itemView: View?) : RecyclerView.ViewHolder(itemView!!) {
abstract fun bind(`object`: BaseInterface?)
}interface BaseInterface {
val viewType: Int
}
Ayrıca bind metodunda BaseInterfaceden implement olan objemizi cast ederek içerisindeki mevcut datayı kullanabilmemiz için tabii ki Product nesnesinin de BaseInterface’den implement edilmesi gerekiyor.
data class Product : BaseInterface{ var productName : String? = null
var productPrice : BigDecimal? = null
override val viewType: Int
get() = ModelType.PRODUCTS
override fun equals(other: Any?): Boolean {
return this.id == (other as Product).id
}
}
ViewHolderı nesnemizle birlikte çoklayabiliriz. Örneğin mesajlar için MessagesViewHolder yazmamız gerekir. Ona ait işlemleri bu holderın içinde yürütebiliriz. Holderda daha fazla Generic bir yapı kurabiliriz. Ancak fazla karmaşık yapılar oluşturmayı sevmiyorum. Çünkü bunun fazlası kodun komplike görünmesine, çok fazla if-else koşullanması içermesine neden oluyor ve bu da kodun yönetilmesini ve bakımını zorlaştırıyor.
Yine kod tekrarını önlemek, ortak bir kütüphane yazıp onun nimetlerinden hemen hemen her projede yararlanmak, kodun bakımını kolaylaştırmak ve test edilebilirliğini arttırmak istiyorsak projelerimizde generic bir yapı kurmalıyız.
Umarım faydalı bir yazı olmuştur.
Sağlıkla kalın!
Kodla kalın! ;)