I'm developing a Kotlin Android app and I'm having an issue integrating Google Sign In, when I get the GoogleSignInAccount in order to extract properties, the property names seem to be obfuscated (or otherwise jumbled up), here's a screenshot of the way the properties look on AS 2.3 debugger:
Here's the snippet of code that tries to access those properties:
private fun googleSignInResult(data : GoogleSignInResult) {
if (data.isSuccess) {
if (data.signInAccount != null) {
val account = data.signInAccount
val authData = HashMap<String, String>()
authData["id_token"] = account?.idToken.let { it } ?: return
authData["id"] = account?.id.let { it } ?: return
val task = ParseUser.logInWithInBackground("google", authData)
task.continueWith { user ->
if (task.isCancelled) {
Log.d(TAG, "User cancelled Google login")
} else if (task.isFaulted) {
Log.d(TAG, "Failed: " + task.error)
} else {
this.user = task.result
this.user?.put("email", account?.email)
this.user?.put("name", account?.displayName)
this.user?.put("firstName", account?.displayName)
this.user?.saveInBackground({ error ->
if(error != null) {
Log.d(TAG, "Error: " + error.message)
this.user?.deleteInBackground()
ParseUser.logOutInBackground()
} else {
//Logged in successfully
}
})
}
}
}
}
}
Can anyone shed some light on why is it that properties look like that?, when I try to access idToken or id they're always null, however, the property names that are "obfuscated" can't be accessed, is this a kotlin bug or is it my error?
Any help will be much appreciated!
The following content is originally posted by #EugenPechanec as a comment to the question body. Some modification is applied to promote reading experience.
Fields, a term from JVM, is not properties, which is a Kotlin term. You're on JVM, and what you see in the debugger are obfuscated fields backing the Kotlin properties. The getters are public and retain original names. Java .getDisplayName() is .displayName in Kotlin.
Related
In my project I am trying to integrate new version (5.0) of google billing lib, I am following the google example
https://codelabs.developers.google.com/play-billing-codelab#3
as an example there are two functions:
fun queryPurchases() {
if (!billingClient.isReady) {
Log.e(TAG, "queryPurchases: BillingClient is not ready")
}
// Query for existing subscription products that have been purchased.
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS).build()
) { billingResult, purchaseList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
if (!purchaseList.isNullOrEmpty()) {
_purchases.value = purchaseList
} else {
_purchases.value = emptyList()
}
} else {
Log.e(TAG, billingResult.debugMessage)
}
}
}
which should return purchases that the user has previously made and another function is
fun queryProductDetails() {
val params = QueryProductDetailsParams.newBuilder()
val productList = mutableListOf<QueryProductDetailsParams.Product>()
for (product in LIST_OF_PRODUCTS) {
productList.add(
QueryProductDetailsParams.Product.newBuilder()
.setProductId(product)
.setProductType(BillingClient.ProductType.SUBS)
.build()
)
params.setProductList(productList).let { productDetailsParams ->
Log.i(TAG, "queryProductDetailsAsync")
billingClient.queryProductDetailsAsync(productDetailsParams.build(), this)
}
}
}
where as a result I expect to get available products, however, those two functions return empty lists as a result.
I know that these products exist as before the new lib version I used the previous one 4.x.x and it worked.
What am I missing here? Any advice appreciates.
So, eventually in my case it was a configuration issues, I was need to do a few changes (just for debug, this shouldn't be in production)
1)
...
buildTypes {
release {
debuggable true
...
delete this line (if you have)
...
applicationIdSuffix '.feature'
...
I just migrated to V5 and it's working for me.
For queryPurchasesAsync are you sure you have subscription products? You're passing in BillingClient.ProductType.SUBS as product type? Maybe this should be BillingClient.ProductType.INAPP. The code looks ok otherwise.
For queryProductDetailsAsync you're calling it in the loop multiple times instead of calling it once your productList is populated. Also the let is unnecessary, there's no reason to create a new scope. Using map you can simplify the code to:
val products = inAppPurchaseProductIds.map { productId ->
QueryProductDetailsParams.Product.newBuilder()
.setProductId(productId)
.setProductType(BillingClient.ProductType.INAPP)
.build()
}
val params = QueryProductDetailsParams.newBuilder().setProductList(products).build()
billingClient.queryProductDetailsAsync(params, this)
Note I used BillingClient.ProductType.INAPP. If you have subscriptions, you'd need to change that to BillingClient.ProductType.SUBS.
Basically I have to make a network request using OkHttp in parallel to various addresses. I only care about the result of the first one that succeeds. Can I do this with Flow on Kotlin?
I've been looking around but I'm struggling with getting the requests to run in parallel, the always run in sequence.
The code basically takes a list of addresses and should return the only address that worked or null if none worked.
Thanks.
Edit: I should mention I plan on using this on Android. I can probably do it with RX but wanted to learn Flow. Also trying to limit the libraries I add to the app.
Edit: I have marked an answer as correct however that isn't how I did but it took me very close to how I did it but since I'm new to Flow I have no idea if how I did it is correct though I'm pretty sure it works after my testing.
I have a function that throws NoSuchElementException when not found. It calls searchForIPAsync which is a suspend function that does all the OkHttp work and returns true|false.
#Throws(NoSuchElementException::class)
private suspend fun findWorkingIP(ipsToTest: MutableList<String>): String? = ipsToTest
.asFlow()
.flatMapMerge(ipsToTest.size)
{ impl ->
flow<String?> {
val res = connectionHelper.searchForIPAsync(getURLToTest(impl))
if (res) {
emit(impl)
} else {
}
}
}.first()
Then I call this and catch the exception in case nothing is found:
try {
val ipFound = findWorkingIP(ipsToTest)
Log.w(TAG, "find: Got something " + ipFound);
return ipFound
} catch (ex: NoSuchElementException) {
Log.w(TAG, "find: not found");
}
Although the Flow-based solution in another answer is a close match to what you need, unfortunately as of Kotlin 1.3.2 the Flow implementation has a bug that breaks it. The bug already has a proposed fix so this should be resolved with the next patch release of Kotlin. In the meantime, here's a similar solution that uses async and Channel instead:
suspend fun getShortUrl(urls: List<String>): String = coroutineScope {
val chan = Channel<String?>()
urls.forEach { url ->
launch {
try {
fetchUrl(url)
} catch (e: Exception) {
null
}.also { chan.send(it) }
}
}
try {
(1..urls.size).forEach { _ ->
chan.receive()?.also { return#coroutineScope it }
}
throw Exception("All services failed")
} finally {
coroutineContext[Job]!!.cancelChildren()
}
}
I'm trying to combine the Android Architecture GitHub example with databinding. To do so, I think I have to add an additional transformation from LiveData> to a LiveData in the UserViewModel:
val userResourceLiveData: LiveData<Resource<User>> = Transformations.switchMap(_login) { login ->
if (login == null) {
AbsentLiveData.create()
}
else {
repository.loadUser(login)
}
}
val userLiveData: LiveData<User> = Transformations.switchMap(userResourceLiveData) { userResource ->
if (userResource == null) { // Error 1 on 'if'
AbsentLiveData.create() // Error 2 on 'create()'
}
else {
MutableLiveData(userResource.data)
}
}
However, there are 2 errors showing up:
1) type inference for control flow expression failed, please specify its type explicitly.
2) Type inference failed: not enough information to infer parameter T in fun create(): LiveData
If I change the code to this:
if (userResource == null) {
AbsentLiveData.create<User>()
}
then switchMap starts complaining:
Type inference failed: Cannot infer type parameter Y in ...
1) Why isn't this working the same? I didn't expect a type definition was required at all, because the mapping for <LiveData<Resource<User>>> worked properly in the same way.
2) How to solve the errors?
3) Might this solution to apply databinding be the wrong approach alltogether?
The commit with this specific issue on GitHub repo
This works for me:
if (userResource == null) {
AbsentLiveData.create<User>()
}
else {
MutableLiveData(userResource.data!!)
}
I try to run flaky test with espresso framework (and with Junit4) on Android Studio.
I want to set how many times it should to repeat.
Before I can use
#FlakyTest(tolerance=5)
// (5 is number for repeat, for example)
But this annotation was deprecated in API level 24. - (link on android.developers.com)
Now is availible new #FlakyTest annotation - without tolerance variable. (link on android.developers.com)
I need to set how many times test can be repeated, but don't know how to do it. Any idea?
This annotation has been deprecated because the entire testing framework was replaced by a new one. Thus, the annotation has been also deprecated in favor of a new one.
Unfortunately, comparing to the old annotation this one can not be used to re-run a failed test. That makes it less useful for a practical purpose.
However, you can still use it for something useful. As the documentation says when running tests you can filter out those that are flaky. To do so you need to adjust a build script:
android {
defaultConfig {
testInstrumentationRunnerArgument "notAnnotation", "android.support.test.filters.FlakyTest"
}
}
More about options can be found here.
I found this online https://gist.github.com/abyx/897229#gistcomment-2851489
You create your own test rule
class RetryTestRule(val retryCount: Int = 3) : TestRule {
private val TAG = RetryTestRule::class.java.simpleName
override fun apply(base: Statement, description: Description): Statement {
return statement(base, description)
}
private fun statement(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
Log.e(TAG, "Evaluating ${description.methodName}")
var caughtThrowable: Throwable? = null
for (i in 0 until retryCount) {
try {
base.evaluate()
return
} catch (t: Throwable) {
caughtThrowable = t
Log.e(TAG, description.methodName + ": run " + (i + 1) + " failed")
}
}
Log.e(TAG, description.methodName + ": giving up after " + retryCount + " failures")
if (caughtThrowable != null)
throw caughtThrowable
}
}
}
}
and then add it to your test, like this
#Rule
#JvmField
val mRetryTestRule = RetryTestRule()
I'm trying to find out how to achieve the combination of "if let + cast" in kotlin:
in swift:
if let user = getUser() as? User {
// user is not nil and is an instance of User
}
I saw some documentation but they say nothing regarding this combination
https://medium.com/#adinugroho/unwrapping-sort-of-optional-variable-in-kotlin-9bfb640dc709
https://kotlinlang.org/docs/reference/null-safety.html
One option is to use a safe cast operator + safe call + let:
(getUser() as? User)?.let { user ->
...
}
Another would be to use a smart cast inside the lambda passed to let:
getUser().let { user ->
if (user is User) {
...
}
}
But maybe the most readable would be to just introduce a variable and use a smart cast right there:
val user = getUser()
if (user is User) {
...
}
Kotlin can automatically figure out whether a value is nil or not in the current scope based on regular if statements with no need for special syntax.
val user = getUser()
if (user != null) {
// user is known to the compiler here to be non-null
}
It works the other way around too
val user = getUser()
if (user == null) {
return
}
// in this scope, the compiler knows that user is not-null
// so there's no need for any extra checks
user.something
In Kotlin you can use the let:
val user = getUser()?.let { it as? User } ?: return
This solution is closest to guard but it may be useful.
In Kotlin you can use:
(getUser() as? User)?.let { user ->
// user is not null and is an instance of User
}
as? is a 'safe' cast operator that returns null instead of throwing an error on failure.
What about this one?
val user = getUser() ?: return