Im my current Android application i am trying to implement the following extension functions to handle any type of intent extra
fun Activity.extraNotNull(key: String): Lazy<String> = lazy {
val value: String? = intent?.extras?.getString(key)
requireNotNull(value) { MISSING_MANDATORY_KEY + key }
}
fun Activity.extraNotNull(key: String): Lazy<Long> = lazy {
val value: Long? = intent?.extras?.getLong(key)
requireNotNull(value) { MISSING_MANDATORY_KEY + key }
}
however i am getting the following compile time error
how can i resolve the conflicting overloads error
As mentioned its not possible to overload methods with identical signatures and different return types - there is no way to differentiate what method you are calling.
A better solution is to make generic function that would support all types, something like this would be a good starting point :
inline fun <reified T> Activity.extraNotNull(key: String): Lazy<T> = lazy {
val value: T? = intent?.extras?.let { x ->
when (T::class) {
String::class -> x.getString(key)
Long::class -> x.getLong(key)
Float::class -> x.getFloat(key)
Double::class -> x.getDouble(key)
else -> throw IllegalArgumentException("not a valid data type ${T::class}")
} as? T
}
requireNotNull(value)
}
Usage :
val s: String by extraNotNull("a")
val l: Long by extraNotNull("b")
val f: Float by extraNotNull("c")
val d: Double by extraNotNull("d")
You can't do that. Method overloading won't work if you only change the return type. You need to add/remove some of the parameters.
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")
}
}
}
I have a flow like this: Flow<List<Transaction>>
Each Transaction object has a Category object
What i want is an extension function that groups the transactions per Category
This is what i've tried:
inline fun <T, K> Flow<Iterable<T>>.groupIterableBy(crossinline keySelector: (T) -> K): Flow<Map<K, MutableList<T>>> = map {
val storage = HashMap<K, MutableList<T>>()
it.map{ element ->
val key = keySelector(element)
if (storage[key] == null){
storage[key] = mutableListOf()
}
storage[key]!!.add(element)
}
return#map storage
}
This works great, but i don't feel like this is programmed in a clean way.
Does anyone have suggestions for making this function cleaner?
Following IR42's comment, this should work. Why not use
Iterable.groupBy()
Example scratch:
data class Tra(
val cat: Cat
)
data class Cat(
val name: String
)
val flow = flowOf(
listOf(
Tra(Cat("A")),
Tra(Cat("B")),
Tra(Cat("D")),
Tra(Cat("B"))
),
listOf(
Tra(Cat("A")),
Tra(Cat("C")),
Tra(Cat("B")),
Tra(Cat("A")),
Tra(Cat("C"))
)
)
inline fun <T, K> Flow<Iterable<T>>.groupIterableBy(crossinline keySelector: (T) -> K): Flow<Map<K, List<T>>> =
map { it.groupBy(keySelector) }
val groupedFlow = flow.groupIterableBy{it.cat.name}
runBlocking {
groupedFlow
.collect {
println(it)
}
}
It prints:
{A=[Tra(cat=Cat(name=A))], B=[Tra(cat=Cat(name=B)), Tra(cat=Cat(name=B))], D=[Tra(cat=Cat(name=D))]}
{A=[Tra(cat=Cat(name=A)), Tra(cat=Cat(name=A))], C=[Tra(cat=Cat(name=C)), Tra(cat=Cat(name=C))], B=[Tra(cat=Cat(name=B))]}
Is this the result you're looking for?
Testing and Mockito beginner here, so I hope it's just some simple error.
EDIT it's most likely due to the functions being suspend functions
My test is crashing beacause Mockito returns null where it should return non-nullable Pair. In live environment this code works fine (no NPE), but I cannot make the test pass with Mockito.
Troubling mock:
val mks = mock(MyKeyStore::class.java)
`when`(mks.createKeyStore(user,pass)).thenReturn(Pair(null, "userExists"))
MyKeyStore.createKeyStore() returns non-nullable Pair
suspend fun createKeyStore(user: String, ksPass: String): Pair<KeyStore?, String>
mks.createKeyStore() is called by UserRepo.createUser()
UserRepo crashes beacuse the test considers ksResult == null, which by defintion is non-nullable. When I change it to nullable the code doesn't compile at all, so I'm thinking it's related to Mockito setup.
class UserRepo(private val myKeyStore: MyKeyStore) {
suspend fun createUser(user: String, p0: String, p1: String): Pair<Boolean, String> =
withContext(Dispatchers.IO) {
return#withContext if (p0 == p1) {
val ksResult = myKeyStore.createKeyStore(user, p0)
ksResult.first?.let { //line where NPE crash leads
val keyResult = myKeyStore.createKeyDB(user, p0)
keyResult.first?.let { Pair(true, keyResult.second) } ?: run { Pair(false, keyResult.second) }
} ?: run { Pair(false, ksResult.second) }
} else Pair(false, myKeyStore.mPasswordsNotMatching)
}
}
full test
#Test
fun createUserFailDueToUserExisting() = runBlocking() {
val user = "user"
val pass = "pass"
val mks = mock(MyKeyStore::class.java)
`when`(mks.createKeyStore(user,pass)).thenReturn(Pair(null, "userExists"))
println(mks.createKeyStore(user,pass)) // this actually prints the pair correctly
val repo = UserRepo(mks)
val result = repo.createUser(user,pass,pass) // NPE crash, but why?
assertFalse(result.first)
assertTrue(result.second == "userExists")
}
How can I configure the mock to return Pair rather than null?
it's because the funtions are suspend functions, specyfing dispatcher in runBlocking fixed it for me
where I found an answer: testing coroutines in Android
#Test
fun createUserFailDueToUserExisting() = runBlocking(Dispatchers.IO) {
val user = "user"
val pass = "pass"
val mks = mock(MyKeyStore::class.java)
`when`(mks.createKeyStore(user, pass)).thenReturn(Pair(null, "userExists"))
println(mks.createKeyStore(user, pass))
val repo = UserRepo(mks)
val result = repo.createUser(user, pass, pass)
assertFalse(result.first)
assertTrue(result.second == "userExists")
}
So here is what I want to do: I have this set of nullable values:
val orderDemand: String? = cell.orderDemand
val orderNumber: Int? = cell.orderNumber
val orderConceal: Boolean? = cell.orderConceal
val scriptCode: String? = cell.scriptCode
val scriptTimestamp: Long? = cell.scriptTimestamp
The thing is that sometimes they do come as null so I can't just initialise the values as Non-null.
I want to create a function or class that can work with potential dozens of values and should look like this:
executeIfNotNull(
cell.orderDemand,
cell.articleImageURL,
cell.articleTitle,
cell.scriptCode,
cell.scriptTimestamp){ orderDemand, orderNumber, orderConceal, scriptCode,
scriptTimestamp ->
doSomethingWithAllTheseNonNullValues(...)
}
I couldn't see any working solution on the internet for actual multiple values, but explicit solutions for 2 or max 3 values. I actually came across this solution for 6 values:
private fun <A,B,C,D,E,F,T> nonNullMultipleCheck(
a:A, b:B, c:C, d:D, e:E, f:F, execute: (A, B, C, D, E, F) -> T) {
if(a!=null && b!=null && c!=null && d!=null && e!=null && f!=null) run {
execute(a, b, c, d, e, f)
}
}
I'm sorry but only a joke of a programmer would ever do that. I am looking for a solution where I can use Array or Set or Vararg where I can give all the nullable values as parameters and have access to the lambda result type by name ({ orderDemand, orderNumber, orderConceal, scriptCode, scriptTimestamp ->) just as I described in the function model above. How can I make this work with as many values as I want? Thanks in advance!
Look, to be honest I do not think that such method will solve your problem. Because if two arguments are of the same type, how would you know which argument was null and which was not to execute your doSomethingWithAllTheseNonNullValues() method?
Nevertheless, here is a working example.
fun <T> executeWithoutNulls(vararg args: Any?, execute: (Array<Any>) -> T) {
val filterArguments = args.toList().mapIndexed { index, any ->
if (any != null) any
else null
}.filterNotNull()
execute(filterArguments.toTypedArray())
}
You can pass as many characters as you like and then execute with as many not values as you find.
Example:
fun example() {
val orderDemand: String? = "orderDemand"
val orderNumber: Int? = null
val orderConceal: Boolean? = true
val scriptCode: String? = null
val scriptTimestamp: Long? = 100L
executeWithoutNulls(orderDemand, orderNumber, orderConceal, scriptCode, scriptTimestamp) {
it.forEach {
println("Example - value: ${it}")
}
}
}
Output
Example - value: orderDemand
Example - value: true
Example - value: 100
If you would like to preserve the order of the arguments, in order to know which one is which, then I would suggest the following modifications to the method.
fun <T> executeWithoutNullsIndexed(vararg args: Any?, execute: (Array<Pair<Int, Any>>) -> T) {
val filterArguments = args.toList().mapIndexed { index, any ->
if (any != null) Pair(index, any)
else null
}.filterNotNull()
execute(filterArguments.toTypedArray())
}
Example
fun example() {
val orderDemand: String? = "orderDemand"
val orderNumber: Int? = null
val orderConceal: Boolean? = true
val scriptCode: String? = null
val scriptTimestamp: Long? = 100L
executeWithoutNullsIndexed(orderDemand, orderNumber, orderConceal, scriptCode, scriptTimestamp) {
it.forEach {
println("Example - Index: ${it.first} with value: ${it.second}")
}
}
}
Output
Example - Index: 0 with value: orderDemand
Example - Index: 2 with value: true
Example - Index: 4 with value: 100
I have to make an async request, and then notify the result to the listeners involved.
fun connectToTopic(topic:String, body:(topic:String, data : ByteArray ) -> Void){
topicCallbackMap.put(topic, body) // is this possible???
}
I want to create a map from "topic" to the higher order function, so that I can call the particular higher order function for a particular topic, like this
private val topicCallbackMap: Map<String, body:(topic:String, data : ByteArray ) -> Void>
The above one is a wrong code, just wanted to give the essence.
What I want can be easily achieved by using an interface listener, but I wanted to know if this is possible in Kotlin. Thank you.
It is possible. There are only some syntax errors on your code. And note that you need a MutableMap in order to put value into the map.
private val topicCallbackMap = mutableMapOf<String, (String, ByteArray) -> Unit>()
fun connectToTopic(topic:String, body: (String, ByteArray) -> Unit) {
topicCallbackMap.put(topic, body)
//OR
topicCallbackMap[topic] = body
}
yes this is possible:
val functionMap: Map<String, (Int) -> Int> =
mapOf("a2" to { a: Int -> a * 2 },
"a3" to {a: Int -> a * 3} )
fun execute(a: Int, myBlock: (Int) -> Int) {
println( myBlock(a) )
}
Than you can get the function out of the map and use it as parameter for another function:
val fun1 = functionMap["a2"]
if (fun1 != null) {
execute(3, fun1)
}
Hope this may help some other person. Here it is, more readable code with typealias.
private val topicCallbackMap = mutableMapOf<String,SomeFunc>()
fun connectToTopic(topic:String, body: SomeFunc) {
topicCallbackMap[topic] = body
}
typealias SomeFunc = (String, ByteArray) -> Unit
fun execution(){
//Adding to Map
connectToTopic("foo"){a:String,b:ByteArray-> println(a)}
connectToTopic("bar"){a:String,b:ByteArray-> println(a+b.joinToString("")) }
//Execution
topicCallbackMap["foo"]?.invoke("Foo Printed:", byteArrayOf())
topicCallbackMap["bar"]?.invoke("Bar Printed printed with ByteArray:", byteArrayOf(12))
}
Output:
Foo Printed
Bar Printed printed with ByteArray:12