我目前正在尝试编写一个Android应用程序,该应用程序可以调用API来检索进站巴士的预计到达时间。ViewModel文件显示我使用我的Bus Repository进行API调用,其中listResult包含我想要的数据。
我如何将我所做的API调用传递到我的Android应用程序的某个屏幕?我应该使用UiState变量来完成此操作,对吗?谢谢。
该Pastebin包含我的代码,以及如果它更容易看到那里!!
应用程序视图模型.kthttps://pastebin.com/qPVrDF9i
package com.example.busexpress.ui.screens
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
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.busexpress.BusExpressApplication
import com.example.busexpress.data.SingaporeBusRepository
import com.example.busexpress.network.SingaporeBus
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
/**
* [AppViewModel] holds information about a cupcake order in terms of quantity, flavor, and
* pickup date. It also knows how to calculate the total price based on these order details.
*/
class AppViewModel(private val singaporeBusRepository: SingaporeBusRepository): ViewModel() {
/** The mutable State that stores the status of the most recent request */
var busUiState: BusUiState by mutableStateOf(BusUiState.Loading) // Loading as Default Value
// Setter is private to protect writes to the busUiState
private set
/**
* Call init so we can display status immediately.
*/
init {
getBusTimings(null)
}
fun getBusTimings(userInput: String?) {
// Determine if UserInput is a BusStopCode
var busStopCode: String?
var busServiceNumber: String?
val userInputLength = userInput?.length ?: 0
if (userInputLength == 5) {
// Bus Stop Code
busStopCode = userInput
busServiceNumber = null
}
else {
// Bus Service Number
busStopCode = null
busServiceNumber = userInput
}
// Launch the Coroutine using a ViewModelScope
viewModelScope.launch {
busUiState = BusUiState.Loading
// Might have Connectivity Issues
busUiState = try {
// Within this Scope, use the Repository, not the Object to access the Data, abstracting the data within the Data Layer
val listResult: SingaporeBus = singaporeBusRepository.getBusTimings(
busServiceNumber = busServiceNumber,
busStopCode = busStopCode
)
// Assign results from backend server to busUiState {A mutable state object that represents the status of the most recent web request}
BusUiState.Success(timings = listResult)
}
catch (e: IOException) {
BusUiState.Error
}
catch (e: HttpException) {
BusUiState.Error
}
}
}
// Factory Object to retrieve the singaporeBusRepository and pass it to the ViewModel
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = (this[APPLICATION_KEY] as BusExpressApplication)
val singaporeBusRepository = application.container.singaporeBusRepository
AppViewModel(singaporeBusRepository = singaporeBusRepository)
}
}
}
// private val _uiState = MutableStateFlow(AppUiState(65199))
// val uiState: StateFlow<AppUiState> = _uiState.asStateFlow()
}
// Simply saving the UiState as a Mutable State prevents us from saving the different status
// like Loading, Error, and Success
sealed interface BusUiState {
data class Success(val timings: SingaporeBus) : BusUiState
// The 2 States below need not set new data and create new objects, which is why an object is sufficient for the web response
object Error: BusUiState
object Loading: BusUiState
// Sealed Interface used instead of Interface to remove Else Branch
}
示例:默认屏幕.kthttps://pastebin.com/UiZPwZHG
package com.example.busexpress.ui.screens
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.busexpress.BusExpressApp
import com.example.busexpress.BusExpressScreen
import com.example.busexpress.R
import com.example.busexpress.network.SingaporeBus
import com.example.busexpress.ui.component.BusStopComposable
@Composable
fun DefaultScreen(
busUiState: BusUiState,
modifier: Modifier = Modifier,
appViewModel: AppViewModel = viewModel(),
// navController: NavController
) {
// Mutable State for User Input
var userInput = remember {
mutableStateOf(TextFieldValue(""))
}
Column(
modifier = modifier
.fillMaxWidth()
.padding(16.dp),
) {
// Search Field for Bus Stop or Bus Numbers
SearchView(
label = R.string.search_field_instructions,
state = userInput,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Search
),
onKeyboardSearch = {
appViewModel.getBusTimings(userInput.value.text)
// navController.navigate(BusExpressScreen.Search.name)
}
)
val busArrivalsJson = appViewModel.getBusTimings(userInput.value.text)
when(busUiState) {
is BusUiState.Success -> ResultScreen(busUiState = busUiState, busArrivalsJSON = busArrivalsJson)
is BusUiState.Loading -> LoadingScreen()
is BusUiState.Error -> ErrorScreen()
else -> ErrorScreen()
}
}
}
@Composable
fun LoadingScreen(modifier: Modifier = Modifier) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.fillMaxSize()
) {
Image(
modifier = Modifier.size(200.dp),
painter = painterResource(id = R.drawable.loading_img),
contentDescription = stringResource(R.string.loading_flavor_text)
)
}
}
@Composable
fun ErrorScreen(modifier: Modifier = Modifier) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier.fillMaxSize()
) {
Text(text = stringResource(R.string.loading_failed_flavor_text))
}
}
/**
* The home screen displaying result of fetching photos.
*/
@Composable
fun ResultScreen(
busUiState: BusUiState,
busArrivalsJSON: SingaporeBus
modifier: Modifier = Modifier,
) {
// Results of Search
BusStopComposable(
busArrivalsJSON = busArrivalsJSON,
modifier = modifier
)
// Box(
// contentAlignment = Alignment.Center,
// modifier = modifier.fillMaxSize()
// ) {
// Text(busUiState.toString())
// }
}
@Composable
fun SearchView(
@StringRes label: Int,
state: MutableState<TextFieldValue>,
modifier: Modifier = Modifier,
keyboardOptions: KeyboardOptions,
onKeyboardSearch: () -> Unit,
) {
Column() {
TextField(
value = state.value,
onValueChange = {value ->
state.value = value
},
label = {
if (state.value == TextFieldValue("")) {
Text(
stringResource(id = label),
modifier = Modifier
.fillMaxWidth(),
style = MaterialTheme.typography.h6
)
}
},
modifier = modifier
.fillMaxWidth(),
singleLine = true,
// Search Icon at the Start for Aesthetics
leadingIcon = {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = null,
modifier = Modifier.padding(10.dp)
)
},
// Cancel Button to delete all Input
trailingIcon = {
// Icon appears iif the Search Field is not Empty
if (state.value != TextFieldValue("")) {
IconButton(onClick = {
// Clear the Search Field
state.value = TextFieldValue("")
}) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = "Delete all User Input",
modifier = Modifier.padding(10.dp)
)
}
}
},
keyboardActions = KeyboardActions(
onSearch = { onKeyboardSearch() }
),
keyboardOptions = keyboardOptions,
shape = RoundedCornerShape(25)
)
Row() {
Spacer(modifier = modifier.weight(3f))
// Button for User to Click to begin Search
Button(
onClick = {
// TODO Pass the User Query to the Search Function
onKeyboardSearch()
},
modifier = modifier
.align(Alignment.CenterVertically)
.padding(2.dp)
.weight(1f),
) {
Text(text = stringResource(R.string.search_button_flavor_text))
}
}
}
}
BusApi.kt(包含进行API调用的函数)https://pastebin.com/miAt8x8H
package com.example.busexpress.network
import com.example.busexpress.LTA_API_SECRET_KEY
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Query
interface BusApiService {
/**
* Function to get JSON Objects from URI by specifying Type of Request and Endpoint like "/photos" a URL of sorts
*/
// 1. Returns Bus Timings for Bus Stop and/or Service Number
@Headers(
"accept: application/json",
"AccountKey: $LTA_API_SECRET_KEY"
)
@GET("BusArrivalv2")
suspend fun getTimingsOfBusStop(
@Query("BusStopCode") BusStopCode: String? = null,
@Query("ServiceNo") ServiceNo: String? = null
): SingaporeBus
// 2. Returns the details for all the Bus Stops in Singapore
@Headers(
"accept: application/json",
"AccountKey: $LTA_API_SECRET_KEY"
)
@GET("BusStops")
suspend fun getDetailsOfBusStop(): BusStop
}
1条答案
按热度按时间t40tm48m1#
您可以根据需要使用SharedViewModel在公共父Activity托管的片段之间或父Activity及其子片段之间共享数据。
例如,您可以在SharedViewModel中声明一个MutableStateFlow变量,在收到来自API调用(您从一个片段或父Activity触发)的响应时设置其值,然后通过子片段内的相同SharedViewModel收集该流的值,并相应地更新UI。
Refer https://developer.android.com/codelabs/basic-android-kotlin-training-shared-viewmodel#0