kotlin 如何使用UiState变量将API调用从一个屏幕传递到另一个屏幕?

cmssoen2  于 2022-12-27  发布在  Kotlin
关注(0)|答案(1)|浏览(127)

我目前正在尝试编写一个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
    
}
t40tm48m

t40tm48m1#

您可以根据需要使用SharedViewModel在公共父Activity托管的片段之间或父Activity及其子片段之间共享数据。
例如,您可以在SharedViewModel中声明一个MutableStateFlow变量,在收到来自API调用(您从一个片段或父Activity触发)的响应时设置其值,然后通过子片段内的相同SharedViewModel收集该流的值,并相应地更新UI。
Refer https://developer.android.com/codelabs/basic-android-kotlin-training-shared-viewmodel#0

相关问题