This repository has been archived by the owner on Aug 16, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 260
/
MainViewModel.kt
146 lines (129 loc) · 4.28 KB
/
MainViewModel.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*
* Copyright (C) 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.kotlincoroutines.main
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.android.kotlincoroutines.util.singleArgViewModelFactory
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
/**
* MainViewModel designed to store and manage UI-related data in a lifecycle conscious way. This
* allows data to survive configuration changes such as screen rotations. In addition, background
* work such as fetching network results can continue through configuration changes and deliver
* results after the new Fragment or Activity is available.
*
* @param repository the data source this ViewModel will fetch results from.
*/
class MainViewModel(private val repository: TitleRepository) : ViewModel() {
companion object {
/**
* Factory for creating [MainViewModel]
*
* @param arg the repository to pass to [MainViewModel]
*/
val FACTORY = singleArgViewModelFactory(::MainViewModel)
}
/**
* Request a snackbar to display a string.
*
* This variable is private because we don't want to expose MutableLiveData
*
* MutableLiveData allows anyone to set a value, and MainViewModel is the only
* class that should be setting values.
*/
private val _snackBar = MutableLiveData<String?>()
/**
* Request a snackbar to display a string.
*/
val snackbar: LiveData<String?>
get() = _snackBar
/**
* Update title text via this LiveData
*/
val title = repository.title
private val _spinner = MutableLiveData<Boolean>(false)
/**
* Show a loading spinner if true
*/
val spinner: LiveData<Boolean>
get() = _spinner
/**
* Count of taps on the screen
*/
private var tapCount = 0
/**
* LiveData with formatted tap count.
*/
private val _taps = MutableLiveData<String>("$tapCount taps")
/**
* Public view of tap live data.
*/
val taps: LiveData<String>
get() = _taps
/**
* Respond to onClick events by refreshing the title.
*
* The loading spinner will display until a result is returned, and errors will trigger
* a snackbar.
*/
fun onMainViewClicked() {
refreshTitle()
updateTaps()
}
private fun updateTaps() {
viewModelScope.launch {
delay(1_000)
_taps.value = "${++tapCount} taps"
}
}
/**
* Called immediately after the UI shows the snackbar.
*/
fun onSnackbarShown() {
_snackBar.value = null
}
/**
* Refresh the title, showing a loading spinner while it refreshes and errors via snackbar.
*/
fun refreshTitle() = launchDataLoad {
repository.refreshTitle()
}
/**
* Helper function to call a data load function with a loading spinner, errors will trigger a
* snackbar.
*
* By marking `block` as `suspend` this creates a suspend lambda which can call suspend
* functions.
*
* @param block lambda to actually load data. It is called in the viewModelScope. Before calling the
* lambda the loading spinner will display, after completion or error the loading
* spinner will stop
*/
private fun launchDataLoad(block: suspend () -> Unit): Unit {
viewModelScope.launch {
try {
_spinner.value = true
block()
} catch (error: TitleRefreshError) {
_snackBar.value = error.message
} finally {
_spinner.value = false
}
}
}
}