-
-
Notifications
You must be signed in to change notification settings - Fork 368
/
LiveDataActivity.kt
233 lines (217 loc) · 11.9 KB
/
LiveDataActivity.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package com.example.jetpackcompose.state.livedata
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ListItem
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModelProvider
import com.example.jetpackcompose.core.Person
import com.example.jetpackcompose.core.getSuperheroList
import com.example.jetpackcompose.image.NetworkImageComponentPicasso
class LiveDataActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Just initialized the view model that we want to use in this example. Ideally, we would
// like to inject this view model but to keep things simple, I'm just using a simple way
// to initialize the UsersViewModel.
val viewModel = ViewModelProvider(this).get(SuperheroesViewModel::class.java)
// This is an extension function of Activity that sets the @Composable function that's
// passed to it as the root view of the activity. This is meant to replace the .xml file
// that we would typically set using the setContent(R.id.xml_file) method. The setContent
// block defines the activity's layout.
setContent {
LiveDataComponent(viewModel.superheroes)
}
}
}
// We represent a Composable function by annotating it with the @Composable annotation. Composable
// functions can only be called from within the scope of other composable functions. We should
// think of composable functions to be similar to lego blocks - each composable function is in turn
// built up of smaller composable functions.
@Composable
fun LiveDataComponent(personListLiveData: LiveData<List<Person>>) {
// Here we access the live data object and convert it to a form that Jetpack Compose
// understands using the observeAsState method.
// Reacting to state changes is the core behavior of Compose. We use the state composable
// that is used for holding a state value in this composable for representing the current
// value of the selectedIndex. Any composable that reads the value of counter will be recomposed
// any time the value changes. This ensures that only the composables that depend on this
// will be redraw while the rest remain unchanged. This ensures efficiency and is a
// performance optimization. It is inspired from existing frameworks like React.
val personList by personListLiveData.observeAsState(initial = emptyList())
// Since Jetpack Compose uses the declarative way of programming, we can easily decide what
// needs to shows vs hidden based on which branch of code is being executed. In this example,
// if the personList returned by the live data is empty, we want to show a loading indicator,
// otherwise we want show the appropriate list. So we run the appropriate composable based on
// the branch of code executed and that takes care of rendering the right views.
if (personList.isEmpty()) {
LiveDataLoadingComponent()
} else {
LiveDataComponentList(personList)
}
}
// We represent a Composable function by annotating it with the @Composable annotation. Composable
// functions can only be called from within the scope of other composable functions. We should
// think of composable functions to be similar to lego blocks - each composable function is in turn
// built up of smaller composable functions.
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun LiveDataComponentList(personList: List<Person>) {
// LazyColumn is a vertically scrolling list that only composes and lays out the currently
// visible items. This is very similar to what RecyclerView tries to do as it's more optimized
// than the VerticalScroller.
LazyColumn {
items(
items = personList, itemContent = { person ->
// Card composable is a predefined composable that is meant to represent the
// card surface as specified by the Material Design specification. We also
// configure it to have rounded corners and apply a modifier.
// You can think of Modifiers as implementations of the decorators pattern that are used to
// modify the composable that its applied to. In this example, we assign a padding of
// 16dp to the Card along with specifying it to occupy the entire available width.
Card(
shape = RoundedCornerShape(4.dp),
backgroundColor = Color.White,
modifier = Modifier.fillParentMaxWidth().padding(8.dp)
) {
// ListItem is a predefined composable that is a Material Design implementation of [list
// items](https://material.io/components/lists). This component can be used to achieve the
// list item templates existing in the spec
ListItem(text = {
// The Text composable is pre-defined by the Compose UI library; you can use this
// composable to render text on the screen
Text(
text = person.name,
style = TextStyle(
fontFamily = FontFamily.Serif, fontSize = 25.sp,
fontWeight = FontWeight.Bold
)
)
}, secondaryText = {
Text(
text = "Age: ${person.age}",
style = TextStyle(
fontFamily = FontFamily.Serif, fontSize = 15.sp,
fontWeight = FontWeight.Light, color = Color.DarkGray
)
)
}, icon = {
person.profilePictureUrl?.let { imageUrl ->
// Look at the implementation of this composable in ImageActivity to learn
// more about its implementation. It uses Picasso to load the imageUrl passed
// to it.
NetworkImageComponentPicasso(
url = imageUrl,
modifier = Modifier.width(60.dp).height(60.dp)
)
}
})
}
})
}
}
// We represent a Composable function by annotating it with the @Composable annotation. Composable
// functions can only be called from within the scope of other composable functions. We should
// think of composable functions to be similar to lego blocks - each composable function is in turn
// built up of smaller composable functions.
@Composable
fun LiveDataLoadingComponent() {
// Column is a composable that places its children in a vertical sequence. You
// can think of it similar to a LinearLayout with the vertical orientation.
// In addition we also pass a few modifiers to it.
// You can think of Modifiers as implementations of the decorators pattern that are
// used to modify the composable that its applied to. In this example, we configure the
// Column composable to occupy the entire available width and height using
// Modifier.fillMaxSize().
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
// A pre-defined composable that's capable of rendering a circular progress indicator. It
// honors the Material Design specification.
CircularProgressIndicator(modifier = Modifier.wrapContentWidth(CenterHorizontally))
}
}
// We represent a Composable function by annotating it with the @Composable annotation. Composable
// functions can only be called from within the scope of other composable functions. We should
// think of composable functions to be similar to lego blocks - each composable function is in turn
// built up of smaller composable functions.
@Composable
fun LaunchInCompositionComponent(viewModel: SuperheroesViewModel) {
// Reacting to state changes is the core behavior of Compose. We use the state composable
// that is used for holding a state value in this composable for representing the current
// value of whether the checkbox is checked. Any composable that reads the value of "personList"
// will be recomposed any time the value changes. This ensures that only the composables that
// depend on this will be redraw while the rest remain unchanged. This ensures efficiency and
// is a performance optimization. It is inspired from existing frameworks like React.
val personList = remember { mutableStateListOf<Person>() }
// LaunchedEffect allows you to launch a suspendable function as soon as this composable
// is first committed i.e this tree node is first allowed to be rendered on the screen. It
// also takes care of automatically cancelling it when it is no longer in the composition.
LaunchedEffect(Unit) {
// This view model merely calls a suspendable function "loadSuperheroes" to get a list of
// "Person" objects
val list = viewModel.loadSuperheroes()
// We add it to our state object
personList.addAll(list)
}
// If the list is empty, it means that our coroutine has not completed yet and we just want
// to show our loading component and nothing else. So we return early.
if (personList.isEmpty()) {
LiveDataLoadingComponent()
return
}
// If the personList is available, we will go ahead and show the list of superheroes. We
// reuse the same component that we created above to save time & space :)
LiveDataComponentList(personList)
}
/**
* Android Studio lets you preview your composable functions within the IDE itself, instead of
* needing to download the app to an Android device or emulator. This is a fantastic feature as you
* can preview all your custom components(read composable functions) from the comforts of the IDE.
* The main restriction is, the composable function must not take any parameters. If your composable
* function requires a parameter, you can simply wrap your component inside another composable
* function that doesn't take any parameters and call your composable function with the appropriate
* params. Also, don't forget to annotate it with @Preview & @Composable annotations.
*/
@Preview
@Composable
fun LiveDataComponentListPreview() {
LiveDataComponentList(getSuperheroList())
}
@Preview
@Composable
fun LiveDataLoadingComponentPreview() {
LiveDataLoadingComponent()
}