Skip to content

Commit

Permalink
+ Use DataBinding instead of ViewBinding in order to handle click lis…
Browse files Browse the repository at this point in the history
…teners and different states of loading data inside recyclerView.

+ Handle loading state for article details webView.
  • Loading branch information
Mina Mikhail committed Aug 20, 2021
1 parent 391f47b commit eb374ec
Show file tree
Hide file tree
Showing 27 changed files with 489 additions and 504 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
## Tech stack & News App libraries

- Navigation component - navigation graph for navigating and replacing screens/fragments
- ViewBinding - allows to more easily write code that interacts with views and replaces ```findViewById```.
- DataBinding - allows to more easily write code that interacts with views and replaces ```findViewById```, handle
click listeners and different states of loading data inside recyclerView.
- ViewModel - UI related data holder, lifecycle aware.
- LiveData - Build data objects that notify views when the underlying database changes.
- Dagger-Hilt for dependency injection. Object creation and scoping is handled by Hilt.
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ android {
}
}

buildFeatures {
viewBinding true
dataBinding {
enabled = true
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.mina_mikhail.newsapp.core.enums

object DataStatus {

const val LOADING = 1
const val SHOW_DATA = 2
const val NO_DATA = 3
const val NO_INTERNET = 4
const val LOADING_NEXT_PAGE = 5

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.mina_mikhail.newsapp.core.view

import android.os.Bundle
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.LiveData
import androidx.navigation.NavController
import androidx.viewbinding.ViewBinding

abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
abstract class BaseActivity<VB : ViewDataBinding> : AppCompatActivity() {

private var _binding: VB? = null
open val binding get() = _binding!!
Expand All @@ -33,10 +35,13 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
}

private fun initViewBinding() {
_binding = getViewBinding()
_binding = DataBindingUtil.setContentView(this, getLayoutId())
binding.lifecycleOwner = this
binding.executePendingBindings()
}

abstract fun getViewBinding(): VB
@LayoutRes
abstract fun getLayoutId(): Int

open fun setUpBottomNavigation() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
import com.mina_mikhail.newsapp.core.utils.hideLoadingDialog
import com.mina_mikhail.newsapp.core.utils.showLoadingDialog

abstract class BaseFragment<VB : ViewBinding> : Fragment() {
abstract class BaseFragment<VB : ViewDataBinding> : Fragment() {

private var _binding: VB? = null
open val binding get() = _binding!!
Expand All @@ -28,8 +30,10 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment() {
}

private fun initViewBinding(inflater: LayoutInflater, container: ViewGroup?) {
_binding = getViewBinding(inflater, container)
_binding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false)
mRootView = binding.root
binding.lifecycleOwner = this
binding.executePendingBindings()
}

override
Expand All @@ -51,28 +55,37 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment() {
super.onViewCreated(view, savedInstanceState)

if (!hasInitializedRootView) {
getFragmentArguments()
setBindingVariables()
setUpViews()
observeAPICall()
handleClickListeners()
subscribeToObservables();
setupObservers()

hasInitializedRootView = true
}
}

abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB
@LayoutRes
abstract fun getLayoutId(): Int

open fun registerListeners() {}

open fun unRegisterListeners() {}

open fun getFragmentArguments() {}

open fun setBindingVariables() {}

open fun setUpViews() {}

open fun observeAPICall() {}

open fun handleClickListeners() {}

open fun subscribeToObservables() {}
open fun setupObservers() {

}

fun showLoading() {
hideLoading()
Expand All @@ -85,11 +98,4 @@ abstract class BaseFragment<VB : ViewBinding> : Fragment() {
}

fun hideLoading() = hideLoadingDialog(progressDialog, requireActivity())

override
fun onDestroyView() {
super.onDestroyView()

_binding = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.mina_mikhail.newsapp.core.view

import androidx.lifecycle.ViewModel
import com.mina_mikhail.newsapp.core.utils.SingleLiveEvent

open class BaseViewModel : ViewModel() {

var dataLoadingEvent: SingleLiveEvent<Int> = SingleLiveEvent()

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import dagger.hilt.android.AndroidEntryPoint
class NewsActivity : BaseActivity<ActivityNewsBinding>() {

override
fun getViewBinding() = ActivityNewsBinding.inflate(layoutInflater)
fun getLayoutId() = R.layout.activity_news

override
fun setUpBottomNavigation() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.mina_mikhail.newsapp.features.news.presentation

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.mina_mikhail.newsapp.R
import com.mina_mikhail.newsapp.R.layout
import com.mina_mikhail.newsapp.core.utils.DateUtils
import com.mina_mikhail.newsapp.core.utils.convertDateTimeToTimesAgo
import com.mina_mikhail.newsapp.core.view.extensions.loadRoundImage
Expand All @@ -33,8 +33,10 @@ class NewsAdapter(private var itemClick: (Article) -> Unit) : ListAdapter<Articl
parent: ViewGroup,
viewType: Int
): ArticlesViewHolder {
val root = LayoutInflater.from(parent.context).inflate(R.layout.item_news, parent, false)
return ArticlesViewHolder(root)
val binding = DataBindingUtil.inflate<ItemNewsBinding>(
LayoutInflater.from(parent.context), layout.item_news, parent, false
)
return ArticlesViewHolder(binding)
}

override
Expand All @@ -45,9 +47,8 @@ class NewsAdapter(private var itemClick: (Article) -> Unit) : ListAdapter<Articl

fun getItemByPosition(position: Int): Article = getItem(position)

inner class ArticlesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
inner class ArticlesViewHolder(private val binding: ItemNewsBinding) : RecyclerView.ViewHolder(binding.root) {

private val binding = ItemNewsBinding.bind(itemView)
private var currentItem: Article? = null

init {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
package com.mina_mikhail.newsapp.features.news.presentation.breaking_news

import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.mina_mikhail.newsapp.R
import com.mina_mikhail.newsapp.core.data_source.BaseRemoteDataSource
import com.mina_mikhail.newsapp.core.enums.DataStatus
import com.mina_mikhail.newsapp.core.network.Resource.Empty
import com.mina_mikhail.newsapp.core.network.Resource.Failure
import com.mina_mikhail.newsapp.core.network.Resource.Loading
import com.mina_mikhail.newsapp.core.network.Resource.Success
import com.mina_mikhail.newsapp.core.utils.EndlessRecyclerViewScrollListener
import com.mina_mikhail.newsapp.core.view.BaseFragment
import com.mina_mikhail.newsapp.core.view.extensions.handleApiError
import com.mina_mikhail.newsapp.core.view.extensions.hide
import com.mina_mikhail.newsapp.core.view.extensions.show
import com.mina_mikhail.newsapp.databinding.FragmentBreakingNewsBinding
import com.mina_mikhail.newsapp.features.news.domain.entity.model.Article
import com.mina_mikhail.newsapp.features.news.presentation.NewsAdapter
Expand All @@ -32,8 +29,12 @@ class BreakingNewsFragment : BaseFragment<FragmentBreakingNewsBinding>() {
private lateinit var scrollListener: EndlessRecyclerViewScrollListener

override
fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?) =
FragmentBreakingNewsBinding.inflate(inflater, container, false)
fun getLayoutId() = R.layout.fragment_breaking_news

override
fun setBindingVariables() {
binding.includedList.baseViewModel = viewModel
}

override
fun setUpViews() {
Expand Down Expand Up @@ -80,16 +81,14 @@ class BreakingNewsFragment : BaseFragment<FragmentBreakingNewsBinding>() {
when (it) {
is Loading -> {
if (articlesAdapter.currentList.isNullOrEmpty()) {
showDataLoading()
viewModel.dataLoadingEvent.value = DataStatus.LOADING
} else {
showPaginationLoading()
viewModel.dataLoadingEvent.value = DataStatus.LOADING_NEXT_PAGE
}
}
is Empty -> {
if (articlesAdapter.currentList.isNullOrEmpty()) {
showNoData()
} else {
hidePaginationLoading()
viewModel.dataLoadingEvent.value = DataStatus.NO_DATA
}
}
is Success -> {
Expand All @@ -106,15 +105,10 @@ class BreakingNewsFragment : BaseFragment<FragmentBreakingNewsBinding>() {
articlesAdapter.submitList(articlesAdapter.currentList + it.value.articles)
}

showData()
viewModel.dataLoadingEvent.value = DataStatus.SHOW_DATA
}
is Failure -> {
if (articlesAdapter.currentList.isNullOrEmpty()) {
handleApiError(it, noDataAction = { showNoData() }, noInternetAction = { showNoInternet() })
} else {
handleApiError(it)
hidePaginationLoading()
}
handleApiError(it)
}
}
})
Expand Down Expand Up @@ -150,45 +144,4 @@ class BreakingNewsFragment : BaseFragment<FragmentBreakingNewsBinding>() {
bundle
)
}

private fun showDataLoading() {
binding.includedList.container.show()
binding.includedList.progressBar.show()
binding.includedList.emptyViewContainer.hide()
binding.includedList.internetErrorViewContainer.hide()
binding.includedList.recyclerView.hide()
binding.includedList.paginationProgressBar.hide()
}

private fun showPaginationLoading() {
binding.includedList.paginationProgressBar.show()
}

private fun hidePaginationLoading() {
binding.includedList.paginationProgressBar.hide()
}

private fun showData() {
binding.includedList.recyclerView.show()
binding.includedList.container.hide()
binding.includedList.paginationProgressBar.hide()
}

private fun showNoData() {
binding.includedList.container.show()
binding.includedList.emptyViewContainer.show()
binding.includedList.internetErrorViewContainer.hide()
binding.includedList.progressBar.hide()
binding.includedList.paginationProgressBar.hide()
binding.includedList.recyclerView.hide()
}

private fun showNoInternet() {
binding.includedList.container.show()
binding.includedList.internetErrorViewContainer.show()
binding.includedList.emptyViewContainer.hide()
binding.includedList.progressBar.hide()
binding.includedList.paginationProgressBar.hide()
binding.includedList.recyclerView.hide()
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.mina_mikhail.newsapp.features.news.presentation.breaking_news

import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import com.mina_mikhail.newsapp.core.network.Resource
import com.mina_mikhail.newsapp.core.view.BaseViewModel
import com.mina_mikhail.newsapp.features.news.data.repository.NewsRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import javax.inject.Inject

@HiltViewModel
class BreakingNewsViewModel @Inject constructor(private val repository: NewsRepository) : ViewModel() {
class BreakingNewsViewModel @Inject constructor(private val repository: NewsRepository) : BaseViewModel() {

var shouldLoadMore = false
var isLoading = false
Expand Down
Loading

0 comments on commit eb374ec

Please sign in to comment.