I'm try to register an ActivityResultContract for android.provider.Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE using kotlin and AndroidX:
class AutofillContract() : ActivityResultContract<Any?,ActivityResult>() {
override fun createIntent(context: Context, input: Any?): Intent
= Intent(android.provider.Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
override fun parseResult(resultCode: Int, intent: Intent?): ActivityResult
= ActivityResult(resultCode, intent)
}
val afrl = registerForActivityResult(AutofillContract()) {
if (it.resultCode == RESULT_OK) ...
else ...
}
However, when I try to use it:
afrl.launch(AutofillContract().createIntent(this, null))
I get IllegalArgumentException: Can only use lower 16 bits for requestCode, which I presume was triggered by an internal startActivityForResult() call.
I haven't used a custom ActivityResultContract before, and although it seems simple it also seems a bit sketchy to me -- I'm not sure if regarding the input as irrelevant (Any?) is the way to go, but it does seem irrelevant in this case (the first version used Intent as the input type but there doesn't seem to be a point, and the problem, "Can only use lower 16 bits..." was the same).
I'm using androidx.activity:activity-ktx:1.2.0-alpha08.
As per this issue, you get that error when you are using an older version of Fragments.
You must also upgrade your version of Fragments to androidx.fragment:fragment-ktx:1.3.0-alpha08.
Related
I am trying to emit an event using DeviceEventManagerModule.RCTDeviceEventEmitter in React Native, but because I wish to call it from a Service, I don't know how to get the reactContext. My code looks like this:
class TimerService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val reactContext = ???
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("tick", "tock");
}
}
If the essence of what I am trying to do is wrong I'm happy to change anything and everything.
For more context, what I'm trying to do is keep track of rest timers in-between sets at the gym. Full code can be found here. I wanted to add a page to display the currently running rest timer, where the state is being stored in TimerService.
I tried to cast applicationContext to ReactContext like:
(applicationContext as ReactApplicationContext)
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("tick", "tock");
But was given an error saying I can't cast ApplicationContext to ReactApplicationContext. I also tried to figure out how I could pass reactApplicationContext down to my Service, but got a bit stumped on trying to parcel it for an Intent.
My app consists of playing simple games and earn some points and then withdraw these points to an external account. I am saving these points in the SharedPreferences to avoid multiple requests to the server (I still validate them when the user wants to withdrawn, but this is not relevant for my problem).
Now, the code goes something like this:
UserActiviy
private fun userClickedToWithdraw() {
startActivityForResult(Intent(this, WithdrawActivity::class.java), RequestCode.RC_WITHDRAW)
}
private fun transactionSuccessfully() {
//set the user points to zero in the SharedPreferences
//and updates the UI accordingly
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == RequestCode.RC_WITHDRAW && resultCode == RESULT_OK) transactionSuccessfully()
}
WithdrawActivity
//handles the transaction and then call finishTransaction
private fun finishTransaction() {
setResult(RESULT_OK)
onBackPressed()
}
The user transfers his points, and then the finishTransaction function is called setting the result as OK and finishing the activity. The parent activity handles the onActivityResult and if the result was OK, it updates the SharedPreferences and UI.
This works perfectly on all the devices that I tested, but for some reason, some of my users are having a different outcome. When they transfer successfully, the SharedPreferences and UI are not being updated correctly.
I cannot replicate this behavior so I can fix it. So I was hoping if someone that had some similar problems could lead me here. I am thinking that maybe the resultCode is not RESULT_OK and the transactionSuccessfully is not being called because of that, but as I said, I am not being able to replicate this.
Does anyone has had a similar problem that could help me?
This is just speculation but if possible call
super.onActivityResult(requestCode, resultCode, data)
last. Ideally you want to run it first but I've had issues where it led to unexpected behavior concerning my code inside of my #override.
I want to return my address model as result in Intent. If I try get my address model in onAcivityResult methods all works fine, but in onActivityReenter I got this Exception:
Class not found when unmarshalling: ua.com.uklontaxi.models.UIAddress
java.lang.ClassNotFoundException: ua.com.myapp.models.UIAddress
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:324)
at android.os.Parcel.readParcelableCreator(Parcel.java:2383)
at android.os.Parcel.readParcelable(Parcel.java:2337)
at android.os.Parcel.readValue(Parcel.java:2243)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2592)
at android.os.BaseBundle.unparcel(BaseBundle.java:221)
at android.os.Bundle.getParcelable(Bundle.java:786)
There are not such errors in another places (for example onActivityResult in the same Activity)
How to fix it?
P.S. I put my model to Bundle and then I put this bundle to Intent. I tried put address to Intent without Bundle-wrapping. It doens't help me.
This helps me:
override fun onActivityReenter(resultCode: Int, data: Intent?) {
super.onActivityReenter(resultCode, data)
data?.setExtrasClassLoader(this.classLoader) // this is context!
}
For anyone still wondering why this issue persists only for API versions below Oreo (8), it has to do with a fix that was provided for API 8+. You can find the official fix here or you can check the below code (taken from the official repo):
Intent intent = mEnterActivityOptions.getResultData();
if (intent != null) {
intent.setExtrasClassLoader(activity.getClassLoader());
}
activity.onActivityReenter(result, intent);
It essentially, adds manually the ClassLoader to the Intent, before calling the Activity's onActivityReenter(...);
Here is the stub of the code.
Click data item on ListView . Works as designed and opens Chrome Custom Tab :
onData(anything()).inAdapterView(withId(R.id.listView))
.atPosition(0).perform(click());
Pause(5000);
Espresso.pressBack();
Cannot seem to evaluate anything in the tab or even hit device back button.
Getting this error
Error : android.support.test.espresso.NoActivityResumedException: No
activities in stage RESUMED.
Did you forget to launch the activity. (test.getActivity() or similar)?
You can use UIAutomator (https://developer.android.com/training/testing/ui-automator.html). You can actually use both Espresso and UIAutomator at the same time. See the accepted answer on the following post for more information:
How to access elements on external website using Espresso
You can prevent opening Custom Tabs and then just assert whether the intent you are launching is correct:
fun stubWebView(uri: String) {
Intents.intending(allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW), IntentMatchers.hasData(uri)))
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, null))
}
fun isNavigatedToWebView(uri: String) {
Intents.intended(allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW), IntentMatchers.hasData(uri)))
}
This way you can avoid Espresso.pressBack() in your test.
Note that since these are using Espresso Intents, you need to either use IntentsTestRule or wrap these with Intents.init and release like this
fun intents(func: () -> Unit) {
Intents.init()
try {
func()
} finally {
Intents.release()
}
}
intents {
stubWebView(uri = "https://www.example.com")
doSomethingSuchAsClickingAButton()
isNavigatedToWebView(uri = "https://www.example.com")
}
A suggestion for improved readability following Mustafa answer:
intents{
Intents.intended(
CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasPackage("com.android.chrome")
)
)
}
Is there any good way to test the result code and data in an Android Espresso test? I am using Espresso 2.0.
Suppose I have an Activity called BarActivity.class, which upon performing some action, calls setResult(int resultCode, Intent data) with the appropriate payload.
I'd like to write a test case to verify the resultCode and data. However, because setResult() is a final method, I can't override it.
Some options I thought about were:
Define a new method like setActivityResult() and just use that so it can be intercepted, etc...
Write a test-only TestActivity that will call startActivityForResult() on BarActivity and check the result in TestActivity.onActivityResult()
Trying to think what's lesser of the two evils, or if there's any other suggestions on how to test for this. Any suggestions? Thanks!
If meanwhile you switched to the latest Espresso, version 3.0.1, you can simply use an ActivityTestRule and get the Activity result like this:
assertThat(rule.getActivityResult(), hasResultCode(Activity.RESULT_OK));
assertThat(rule.getActivityResult(), hasResultData(IntentMatchers.hasExtraWithKey(PickActivity.EXTRA_PICKED_NUMBER)));
You can find a working example here.
If you are willing to upgrade to 2.1, then take a look at Espresso-Intents:
Using the intending API (cousin of Mockito.when), you can provide a response for activities that are launched with startActivityForResult
This basically means it is possible to build and return any result when a specific activity is launched (in your case the BarActivity class).
Check this example here: https://google.github.io/android-testing-support-library/docs/espresso/intents/index.html#intent-stubbing
And also my answer on a somewhat similar issue (but with the contact picker activity), in which I show how to build a result and send it back to the Activity which called startActivityForResult()
this works for me:
#Test
fun testActivityForResult(){
// Build the result to return when the activity is launched.
val resultData = Intent()
resultData.putExtra(KEY_VALUE_TO_RETURN, true)
// Set up result stubbing when an intent sent to <ActivityB> is seen.
intending(hasComponent("com.xxx.xxxty.ActivityB")) //Path of <ActivityB>
.respondWith(
Instrumentation.ActivityResult(
RESULT_OK,
resultData
)
)
// User action that results in "ActivityB" activity being launched.
onView(withId(R.id.view_id))
.perform(click())
// Assert that the data we set up above is shown.
onView(withId(R.id.another_view_id)).check(matches(matches(isDisplayed())))
}
Assuming a validation like below on onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
data?.getBooleanExtra(KEY_VALUE_TO_RETURN, false)?.let {showView ->
if (showView) {
another_view_id.visibility = View.VISIBLE
}else{
another_view_id.visibility = View.GONE
}
}
}
I follow this guide as reference : https://developer.android.com/training/testing/espresso/intents and also i had to check this links at the end of the above link https://github.com/android/testing-samples/tree/master/ui/espresso/IntentsBasicSample
and
https://github.com/android/testing-samples/tree/master/ui/espresso/IntentsAdvancedSample
If you're using ActivityScenario (or ActivityScenarioRule) as is the current recommendation in the Android Developers documentation (see the Test your app's activities page), the ActivityScenario class offers a getResult() method which you can assert on as follows:
#Test
fun result_code_is_set() {
val activityScenario = ActivityScenario.launch(BarActivity::class.java)
// TODO: execute some view actions which cause setResult() to be called
// TODO: execute a view action which causes the activity to be finished
val activityResult = activityScenario.result
assertEquals(expectedResultCode, activityResult.resultCode)
assertEquals(expectedResultData, activityResult.resultData)
}