Generic list sayfaları yapalım

Firuze Gümüş
3 min readMay 18, 2020

--

Az kod, çok iş

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.
})
Şu Adapter yazma işinden de kurtulduk mu tamamdır :)

Ş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! ;)

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Firuze Gümüş
Firuze Gümüş

No responses yet