I'm using Jetpack Compose Navigation to pass a Health instance to another composable. The below code shows my health class and my Destination.
Health.kt:
data class Health(
val height: Int,
val weight: Int,
val age: Int,
val gender: Gender
) : Serializable
enum class Gender: Serializable { Male, Female }
NavDestination.kt
composable(
route = "result/{health}",
arguments = listOf(
navArgument("health") {
type =
NavType.SerializableType(Health::class.java)
}
)
) { backStackEntry ->
val health = (backStackEntry.arguments?.getSerializable("health") as? Health) ?: return#composable
ResultScreen(navActions = navActions, health = health)
}
NavActions.kt
val navigateToResultScreen = { health: Health ->
navController.navigate("result/{$health}")
}
However, I'm getting this error according to the logcat. Any assistance would be highly appreciated.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.octagon_technologies.bmicalculator, PID: 20788
java.lang.UnsupportedOperationException: Serializables don't support default values.
at androidx.navigation.NavType$SerializableType.parseValue(NavType.java:838)
at androidx.navigation.NavType$SerializableType.parseValue(NavType.java:791)
at androidx.navigation.NavType.parseAndPut(NavType.java:96)
at androidx.navigation.NavDeepLink.parseArgument(NavDeepLink.java:299)
at androidx.navigation.NavDeepLink.getMatchingArguments(NavDeepLink.java:260)
at androidx.navigation.NavDestination.matchDeepLink(NavDestination.java:474)
at androidx.navigation.NavGraph.matchDeepLink(NavGraph.java:79)
at androidx.navigation.NavController.navigate(NavController.java:1034)
at androidx.navigation.NavController.navigate(NavController.java:1017)
at androidx.navigation.compose.NavHostControllerKt.navigate(NavHostController.kt:107)
at androidx.navigation.compose.NavHostControllerKt.navigate$default(NavHostController.kt:106)
at com.octagon_technologies.bmicalculator.ui.navigation.NavActions$navigateToResultScreen$1.invoke(NavActions.kt:9)
at com.octagon_technologies.bmicalculator.ui.navigation.NavActions$navigateToResultScreen$1.invoke(NavActions.kt:8)
at com.octagon_technologies.bmicalculator.ui.screens.home.HomeScreenKt$HomeScreen$1$6.invoke(HomeScreen.kt:122)
at com.octagon_technologies.bmicalculator.ui.screens.home.HomeScreenKt$HomeScreen$1$6.invoke(HomeScreen.kt:120)
at com.octagon_technologies.bmicalculator.ui.components.home.CalculateBtnKt$CalculateButton$1$1$1.invoke(CalculateBtn.kt:29)
at com.octagon_technologies.bmicalculator.ui.components.home.CalculateBtnKt$CalculateButton$1$1$1.invoke(CalculateBtn.kt:29)
Why this couldn't work: https://issuetracker.google.com/issues/148523779
"Serializable and Parcelable have no consistent API surface that would allow automatic parsing of a string into your custom class instance. That's why parseValue throws an UnsupportedOperationException."
It's expected that you'll only pass ids in this deep links, not full objects
Workaround:
composable(
route = "result",
arguments = listOf(
navArgument("health") {
type =
NavType.SerializableType(Health::class.java)
}
)
) {
val health = (navController.previousBackStackEntry.arguments?.getSerializable("health") as? Health) ?: return#composable
ResultScreen(navActions = navActions, health = health)
}
Navigate like this:
navController.currentBackStackEntry
?.arguments?.putSerializable("health", health)
navController.navigate("result")
I have the same issue, but I've fixed it using the Gson library, which is used for encoding & decoding classes from JSON.
Will to use it you will convert your Class into JSON as a String and pass it into a navigation argument as a String type then you'll decode it from String which the JSON code into your Class again.
Example of use
navController.navigate("result/${Gson().toJson($health)}")
Now for decoding our Class again
composable(
route = "result/{health}",
arguments = listOf(navArgument("health") { type = NavType.StringType })
) { backStackEntry ->
val healthAsJson = backStackEntry.arguments?.getString("health")
val health = Gson().fromJson(healthAsJson, Health::class.java)
ResultScreen(navActions = navActions, health = health)
}
Related
Did anyone implement google autocomplete suggestion text field or fragment in a jetpack compose project? If so kindly guide or share code snippets as I'm having difficulty in implementing it.
Update
Here is the intent that I'm triggering to open full-screen dialog, but when I start typing within it gets closed, and also I'm unable to figure out what the issue is and need a clue about handling on activity result for reading the result of the predictions within this compose function.
Places.initialize(context, "sa")
val fields = listOf(Place.Field.ID, Place.Field.NAME)
val intent = Autocomplete.IntentBuilder(
AutocompleteActivityMode.FULLSCREEN,fields).build(context)
startActivityForResult(context as MainActivity,intent, AUTOCOMPLETE_REQUEST_CODE, Bundle.EMPTY)
I am using the MVVM architecture and this is how I implemented it:
GooglePlacesApi
I've created an api for reaching google api named GooglePlacesApi
interface GooglePlacesApi {
#GET("maps/api/place/autocomplete/json")
suspend fun getPredictions(
#Query("key") key: String = <GOOGLE_API_KEY>,
#Query("types") types: String = "address",
#Query("input") input: String
): GooglePredictionsResponse
companion object{
const val BASE_URL = "https://maps.googleapis.com/"
}
}
The #Query("types") field is for specifiying what are you looking for in the query, you can look for establishments etc.
Types can be found here
Models
So I created 3 models for this implementation:
GooglePredictionsResponse
The way the response looks if you are doing a GET request with postman is:
Google Prediction Response
You can see that we have an object with "predictions" key so this is our first model.
data class GooglePredictionsResponse(
val predictions: ArrayList<GooglePrediction>
)
GooglePredictionTerm
data class GooglePredictionTerm(
val offset: Int,
val value: String
)
GooglePrediction
data class GooglePrediction(
val description: String,
val terms: List<GooglePredictionTerm>
)
I only needed that information, if you need anything else, feel free to modify the models or create your own.
GooglePlacesRepository
And finally we create the repository to get the information (I'm using hilt to inject my dependencies, you can ignore those annotations if not using it)
#ActivityScoped
class GooglePlacesRepository #Inject constructor(
private val api: GooglePlacesApi,
){
suspend fun getPredictions(input: String): Resource<GooglePredictionsResponse>{
val response = try {
api.getPredictions(input = input)
} catch (e: Exception) {
Log.d("Rently", "Exception: ${e}")
return Resource.Error("Failed prediction")
}
return Resource.Success(response)
}
}
Here I've used an extra class I've created to handle the response, called Resource
sealed class Resource<T>(val data: T? = null, val message: String? = null){
class Success<T>(data: T): Resource<T>(data)
class Error<T>(message: String, data:T? = null): Resource<T>(data = data, message = message)
class Loading<T>(data: T? = null): Resource<T>(data = data)
}
View Model
Again I'm using hilt so ignore annotations if not using it.
#HiltViewModel
class AddApartmentViewModel #Inject constructor(private val googleRepository: GooglePlacesRepository): ViewModel(){
val isLoading = mutableStateOf(false)
val predictions = mutableStateOf(ArrayList<GooglePrediction>())
fun getPredictions(address: String) {
viewModelScope.launch {
isLoading.value = true
val response = googleRepository.getPredictions(input = address)
when(response){
is Resource.Success -> {
predictions.value = response.data?.predictions!!
}
}
isLoading.value = false
}
}
fun onSearchAddressChange(address: String){
getPredictions(address)
}
}
If you need any further help let me know
I didn't include UI implementation because I assume it is individual but this is the easier part ;)
#Composable
fun MyComponent() {
val context = LocalContext.current
val intentLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
when (it.resultCode) {
Activity.RESULT_OK -> {
it.data?.let {
val place = Autocomplete.getPlaceFromIntent(it)
Log.i("MAP_ACTIVITY", "Place: ${place.name}, ${place.id}")
}
}
AutocompleteActivity.RESULT_ERROR -> {
it.data?.let {
val status = Autocomplete.getStatusFromIntent(it)
Log.i("MAP_ACTIVITY", "Place: ${place.name}, ${place.id}")
}
}
Activity.RESULT_CANCELED -> {
// The user canceled the operation.
}
}
}
val launchMapInputOverlay = {
Places.initialize(context, YOUR_API_KEY)
val fields = listOf(Place.Field.ID, Place.Field.NAME)
val intent = Autocomplete
.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields)
.build(context)
intentLauncher.launch(intent)
}
Column {
Button(onClick = launchMapInputOverlay) {
Text("Select Location")
}
}
}
calculateButton.setOnClickListener {
val panelC = binding.panelCount.text.toString()
val panelS = binding.panelSize.text.toString()
val landS = binding.landSlope.text.toString()
val landSi = binding.landSize.text.toString()
val cit = binding.city.text.toString()
val sun = (1000).toInt()
val air = (1.25).toFloat()
val cel = (25).toInt()
val verim = ((sun * air)/ cel).toString().toDouble()
if (panelC.equals("") || panelS.equals("")|| landS.equals("")|| landSi.equals("")||cit.equals("")){
Toast.makeText(requireContext(),"Alanları Doldurunuz.", Toast.LENGTH_SHORT).show()
}
else{
val action = SignUpCalculateFragmentDirections.actionSignUpCalculateFragmentToDataFragment()
Navigation.findNavController(view).navigate(action)
}
val postMap = hashMapOf<String, Any>()
postMap.put("Panel Sayisi",binding.panelCount.text.toString())
postMap.put("Panel Boyutu",binding.panelSize.text.toString())
postMap.put("Arazi Eğimi",binding.landSlope.text.toString())
postMap.put("Arazi Boyutu",binding.landSize.text.toString())
postMap.put("Şehir",binding.city.text.toString())
postMap.put("date", Timestamp.now())
postMap.put("verim",verim.toString().toDouble())
firestore.collection("Posts").add(postMap).addOnFailureListener {
}.addOnFailureListener {
Toast.makeText(requireContext(),it.localizedMessage,Toast.LENGTH_SHORT).show()
}
There is a calculatePage Fragment Codes. On this page, I am trying to make a yield calculation based on the data I receive from the user. However, I need to add the values that are kept constant in the efficiency calculation, such as "sun", "cel", "air" that I defined in the code. I wrote a random operation there as an example. To see if I can write inside the text I'm trying to print without getting any errors. But the app crashed.
private fun getData(){
firestore.collection("Posts").addSnapshotListener { value, error ->
if (error!=null){
Toast.makeText(requireContext(),error.localizedMessage,Toast.LENGTH_SHORT).show()
}else{
if (value !=null){
if (!value.isEmpty){
val documents = value.documents
for (document in documents){
val pc = document.get("Panel Sayisi") as String
val ps = document.get("Panel Boyutu") as String
val ls = document.get("Arazi Eğimi") as String
val lsi = document.get("Arazi Boyutu") as String
val c = document.get("Şehir") as String
val v = document.get("verim") as Double
val post = Post(pc,ps,ls,lsi,c,v)
postArrayList.add(post)
}
}
}
}
val db = FirebaseFirestore.getInstance()
db.collection("Posts").orderBy("date",Query.Direction.DESCENDING).limit(1)
.get()
.addOnCompleteListener {
val result : StringBuffer = StringBuffer()
if (it.isSuccessful){
for (document in it.result){
result.append(document.data.getValue("verim"))
}
verimText.setText(result)
}
}
On this page, I added the values I defined in my class named 'post' to the postList and added them to Firestore.
data class Post(val pc: String, val ps: String, val ls: String, val lsi: String, val c: String, val v : Double)
#ServerTimestamp
var date: Date? = null
This is my post class
The error is like this: java.lang.NullPointerException: null cannot be cast to non-null
type kotlin.Double
As I explained at the beginning of my question, what I am trying to do is using both the data such as "panelCount", "panelSize" that I get from the user and the "sun", "cel", "air" values that are defined as constants, using the "verimText.setText(result)" in the DataFragment page. I need to show this calculation to the user.
The user enters values such as 'Panel Sayisi', 'Panel Boyutu' that should be used while calculating on the calculation page. I need to show this to the user in verimText using both this data and the 'cel', 'sun', 'air' constants that I wrote in the first code.
PS: verim: 20000 value is the result of hypothetical values that I wrote in the first code. In this part, I need to make a calculation using the other data entered by the user and these constant values and show it in the verimText.
I checked out answers here and here to pass an Object from one composable to another and passing an #Serializable object as String from one composable to another but get the resulting error
java.lang.IllegalArgumentException: Navigation destination that matches request NavDeepLinkRequest{ uri=android-app://androidx.navigation/detail/{"id":3,"email":"emma.wong#reqres.in","first_name":"Emma","last_name":"Wong","avatar":"https://reqres.in/img/faces/3-image.jpg"} } cannot be found in the navigation graph NavGraph(0x0) startDestination={Destination(0x60276fc4) route=start_destination}
at androidx.navigation.NavController.navigate(NavController.kt:1530)
Data class
#Serializable
data class User (
val id : Int,
val email : String,
val first_name : String,
val last_name : String,
val avatar : String
)
it's sample from reqres.in api, and NavHost to navigate between composables is
NavHost(
navController = navController,
startDestination = "start_destination",
modifier = Modifier.padding(paddingValues)
) {
composable(route = "start_destination") {
ListScreen() { user ->
val json: String = Json.encodeToString(user)
navController.navigate("detail/$json")
}
}
composable(route = "detail/{user}", arguments = listOf(
navArgument("user") {
type = NavType.StringType
}
)) { backStackEntry ->
val arguments = requireNotNull(backStackEntry.arguments)
val user = Json.decodeFromString<User>(string = arguments.getString("user")!!)
DetailScreen(user = user)
}
}
crash occurs when navController.navigate("detail/$json") is called.
I've been working on learning RecyclerViews in Kotlin and have become stuck on a specific error that I can't seem to work around:
val itemsHashSet = HashSet(category.items)
produces an error:
None of the following functions can be called with the arguments supplied.
((MutableCollection<out TypeVariable(E)!>..Collection<TypeVariable(E)!>?)) where E = TypeVariable(E) for fun (c: (MutableCollection<out E!>..Collection<E!>?)): kotlin.collections.HashSet /* = java.util.HashSet / defined in kotlin.collections.HashSet
(Int) where E = TypeVariable(E) for fun (initialCapacity: Int): kotlin.collections.HashSet / = java.util.HashSet */ defined in kotlin.collections.HashSet
I've tried changing the format and even writing the code in Java and then converting it in Android Studio to no avail.
Here's the Class I've been stuck on:
class CategoryManager(private val context: Context) {
fun saveCategory(category: CategoryModel) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val editor = sharedPreferences.edit()
val itemsHashSet = HashSet(category.items)
editor.putStringSet(category.name, itemsHashSet)
editor.apply()
}
fun retrieveCategories (): ArrayList<CategoryModel> {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val data = sharedPreferences.all
val categories = ArrayList<CategoryModel>()
for (item in data) {
val category = CategoryModel(item.key, arrayListOf(item.value.toString()))
categories.add(category)
}
return categories
}
}
I'm sorry if I'm a moron and this is obvious, but I can't find an applicable answer here that makes sense. Any help is appreciated
EDIT:
Category Model:
package com.pwneill.favoritelistapp
class CategoryModel (name: String, items: ArrayList<String>) {
val name = name
val items = arrayOf(items)
}
You can initialize HashSet like this:
val itemHashSet = hashSetOf(category.items)
I try to write a network request function,Its role is to analyze the general format and provide data content to callbacks.
This is my defined class:
data class Response<T>(
val code: Int = 0,
#JvmSuppressWildcards
var data: ArrayList<T>,
val message: String = "")
in this class 'code' and 'message' is fixed,'data' has different types
Select one of data:
data class TestData(
#SerializedName("create_by")
val createBy: String = "",
#SerializedName("create_time")
val createTime: String = "",
#SerializedName("name")
val name: String = "",
#SerializedName("id")
val id: String = "")
There is my network request function:
fun <T> post(callBack: (ArrayList<T>) -> Unit) {
...
override fun onSuccess(response:Response<String>) {
val result = gson.fromJson<Response<T>>(response.body().reader(),Response::class.java)
when{
result.code==200-> callBack.invoke(result.data)
}
}
...
}
Use it at Activity:
Request.addr(Constants.GET_TEST)
.post<TestData> {
tv.text = it[0].name
}
When i use Gson parse the server returns data,and want use JavaBean ,Logcat throw this Exception:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.network.response.TestData
i tried to use TypeToken to solve this problem ,but it also does not work.
It cause, because the parser can't fetch the real type T at runtime.
This extension works for me
inline fun Gson.fromJson(json: String) =
this.fromJson(json, object: TypeToken() {}.type)!!
Then you need modify as in you methods, as IDE recommend you.