Skip to content

Commit

Permalink
implement swipe to refresh
Browse files Browse the repository at this point in the history
  • Loading branch information
Tarek-Bohdima committed Oct 17, 2024
1 parent 7750a64 commit 8122c28
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 35 deletions.
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies {
def lifecycle_version = "2.5.1"
def recyclerview_version = "1.2.1"
def navigation_version = "2.5.3"
def swipe_to_refresh_version = "1.1.0"

def test_junit_version = "4.13.2"
def androidTest_junit_version = "1.1.5"
Expand Down Expand Up @@ -99,6 +100,9 @@ dependencies {
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"

// swipe to refresh
implementation "androidx.swiperefreshlayout:swiperefreshlayout:$swipe_to_refresh_version"

// dagger2
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ class DogsApplication: Application() {

override fun onCreate() {
super.onCreate()
appComponent.inject(this)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
package com.example.android.dogsapp.common.di.activity

class ActivityComponent {
import com.example.android.dogsapp.common.di.application.ApplicationComponent
import com.example.android.dogsapp.ui.MainActivity
import com.example.android.dogsapp.ui.main.MainFragment
import dagger.Component

@ActivityScope
@Component(dependencies = [ApplicationComponent::class], modules = [ActivityModule::class])
interface ActivityComponent {
fun inject(mainActivity: MainActivity)
fun inject(mainFragment: MainFragment)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
package com.example.android.dogsapp.common.di.activity

class ActivityModule {
import com.example.android.dogsapp.ui.utils.RefreshManager
import com.example.android.dogsapp.ui.utils.SwipeToRefreshManagerImpl
import dagger.Module
import dagger.Provides

@Module
object ActivityModule {

@Provides
@ActivityScope
fun provideRefreshManager(swipeToRefreshManagerImpl: SwipeToRefreshManagerImpl): RefreshManager {
return swipeToRefreshManagerImpl
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.example.android.dogsapp.common.di.application

import com.example.android.dogsapp.DogsApplication
import com.example.android.dogsapp.data.repository.DogsRepository
import com.example.android.dogsapp.ui.MainActivity
import com.example.android.dogsapp.ui.main.MainFragment
import dagger.Component

@ApplicationScope
@Component(modules = [ApplicationModule::class])
interface ApplicationComponent {
fun inject(mainFragment: MainFragment)
fun inject(application: DogsApplication)

fun inject(mainActivity: MainActivity)

fun dogsRepository(): DogsRepository
}
15 changes: 15 additions & 0 deletions app/src/main/java/com/example/android/dogsapp/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,23 @@ import androidx.databinding.DataBindingUtil
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.NavigationUI
import com.example.android.dogsapp.DogsApplication
import com.example.android.dogsapp.R
import com.example.android.dogsapp.common.di.activity.ActivityComponent
import com.example.android.dogsapp.common.di.activity.DaggerActivityComponent
import com.example.android.dogsapp.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
private lateinit var activityComponent: ActivityComponent

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityComponent = DaggerActivityComponent.builder()
.applicationComponent((application as DogsApplication).appComponent)
.build()

activityComponent.inject(this)

DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

val navHostFragment = supportFragmentManager
Expand All @@ -21,6 +32,10 @@ class MainActivity : AppCompatActivity() {
NavigationUI.setupActionBarWithNavController(this, navController)
}

fun getActivityComponent(): ActivityComponent {
return activityComponent
}

override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.nav_host_fragment)
return NavigationUI.navigateUp(navController, null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import androidx.navigation.fragment.findNavController
import com.example.android.dogsapp.DogsApplication
import com.example.android.dogsapp.data.repository.DogsRepository
import com.example.android.dogsapp.databinding.FragmentMainBinding
import com.example.android.dogsapp.ui.MainActivity
import com.example.android.dogsapp.ui.utils.RefreshManager
import javax.inject.Inject

class MainFragment : Fragment() {
Expand All @@ -20,11 +22,14 @@ class MainFragment : Fragment() {
@Inject
lateinit var dogsRepository: DogsRepository

private val viewModel: MainViewModel by viewModels { MainViewModelFactory(dogsRepository) }
@Inject
lateinit var refreshManager: RefreshManager

private val viewModel: MainViewModel by viewModels { MainViewModelFactory(dogsRepository, refreshManager) }

override fun onAttach(context: Context) {
super.onAttach(context)
(requireActivity().application as DogsApplication).appComponent.inject(this)
(requireActivity() as MainActivity).getActivityComponent().inject(this)
}

override fun onCreateView(
Expand All @@ -35,6 +40,11 @@ class MainFragment : Fragment() {
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel

binding.swipeRefreshLayout.setOnRefreshListener {
viewModel.refreshDogs()
binding.swipeRefreshLayout.isRefreshing = false
}

val adapter = DogsAdapter(DogClickListener { dog ->
viewModel.onDogClicked(dog)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,20 @@ package com.example.android.dogsapp.ui.main
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.example.android.dogsapp.DogsApplication
import com.example.android.dogsapp.data.domain.Dog
import com.example.android.dogsapp.data.repository.DogsRepository
import com.example.android.dogsapp.ui.utils.RefreshManager
import kotlinx.coroutines.launch

enum class DogsApiStatus { LOADING, ERROR, DONE }

class MainViewModel(private val dogsRepository: DogsRepository) : ViewModel() {
class MainViewModel(
private val dogsRepository: DogsRepository,
private val refreshManager: RefreshManager
) : ViewModel() {

private val _status = MutableLiveData<DogsApiStatus>()

val status: LiveData<DogsApiStatus>
get() = _status

Expand All @@ -32,10 +30,10 @@ class MainViewModel(private val dogsRepository: DogsRepository) : ViewModel() {
get() = _navigateToDetail

init {
getRandomDogs()
loadDogsData()
}

private fun getRandomDogs() {
private fun loadDogsData() {
viewModelScope.launch {
_status.value = DogsApiStatus.LOADING
try {
Expand All @@ -60,4 +58,9 @@ class MainViewModel(private val dogsRepository: DogsRepository) : ViewModel() {
fun onDogClicked(dog: Dog) {
_navigateToDetail.value = dog
}

fun refreshDogs() {
loadDogsData()
refreshManager.setRefreshing(false)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ package com.example.android.dogsapp.ui.main
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.android.dogsapp.data.repository.DogsRepository
import com.example.android.dogsapp.ui.utils.RefreshManager
import javax.inject.Inject

@Suppress("UNCHECKED_CAST")
class MainViewModelFactory @Inject constructor(private val dogsRepository: DogsRepository): ViewModelProvider.Factory {
class MainViewModelFactory @Inject constructor(
private val dogsRepository: DogsRepository,
private val refreshManager: RefreshManager
) : ViewModelProvider.Factory {

override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MainViewModel(dogsRepository) as T
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(dogsRepository, refreshManager) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.android.dogsapp.ui.utils

interface RefreshManager {
fun isRefreshing(): Boolean
fun setRefreshing(refreshing: Boolean)
fun setOnRefreshListener(listener: () -> Unit)
fun onRefreshTriggered()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.example.android.dogsapp.ui.utils

import javax.inject.Inject

class SwipeToRefreshManagerImpl @Inject constructor() : RefreshManager {

private var isRefreshing = false
private var refreshListener: (() -> Unit)? = null

override fun isRefreshing() = isRefreshing

override fun setRefreshing(refreshing: Boolean) {
isRefreshing = refreshing
}

override fun setOnRefreshListener(listener: () -> Unit) {
refreshListener = listener
}

override fun onRefreshTriggered() {
if (!isRefreshing) {
isRefreshing = true
refreshListener?.invoke()
}
}
}
44 changes: 25 additions & 19 deletions app/src/main/res/layout/fragment_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,42 @@
android:layout_height="match_parent"
tools:context=".ui.main.MainFragment">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:padding="6dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:listData="@{viewModel.dogs}"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:padding="6dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:listData="@{viewModel.dogs}"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

<ImageView
android:id="@+id/status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/status"
app:DogsApiStatus="@{viewModel.status}"
android:scaleType="centerCrop"
app:DogsApiStatus="@{viewModel.status}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone"/>
tools:visibility="gone" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

0 comments on commit 8122c28

Please sign in to comment.