The description of androidx.activity.result.contract.ActivityResultContract$SynchronousResult said: "An optional method you can implement that can be used to potentially provide a result in lieu of starting an activity."
Let's assume that I want to request for permissions.Generally I have to declare an ActivityResultLauncher by registerForActivityResult(contract, callback), write working logic inside the callback, and launch it in the correct time.
And to the best of my knowledge, with SynchronousResult(), I don't have to register the ActivityResultLauncher. I just need to declare an ActivityResultContract, and calling contract.SynchronousResult() will block untill the result return, i.e., user has made a desicion on the granting of permissions. So I write my code like this:
private boolean requestSDCardPermission(){
if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityResultContracts.RequestMultiplePermissions contract = new ActivityResultContracts.RequestMultiplePermissions();
Map<String, Boolean> synchronousResult=contract.getSynchronousResult(LBL_CloudDisk_MainActivity.this,new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}).getValue();
return synchronousResult.get(Manifest.permission.WRITE_EXTERNAL_STORAGE) && synchronousResult.get(Manifest.permission.READ_EXTERNAL_STORAGE);
}
return true;
}
But when this code executes, a NullPointerException occurs because getValue() point to a null object, which in my opinion means SynchronousResult() didn't block and wait for the result. So how should I properly use this method?
As per that exact set of documentation:
Returns: SynchronousResult<O>: the result wrapped in a ActivityResultContract.SynchronousResult or null if the call should proceed to start an activity.
So it is expected that getSynchronousResult() will return null if you need to call launch() in order to get a valid result back. When it comes to RequestMultiplePermissions, the source code indicates that synchronous results are only returned if all permissions are already granted.
In general, none of the ActivityResultContract APIs are meant to be called directly by your app - they are all called as part of the ActivityResultRegistry and the regular launch that you should be interfacing with. Those automatically do the right thing and will internally call APIs like getSynchronousResult() in order to avoid launching an activity if that isn't needed. You should still be handling the results as part of the callback.
Related
I am trying to store the data I receive from the API call in the variable "data". However, the value of data is only updated inside the jsonObjectRequest and it is empty outside of it. How can I make it so that the value of data is available outside the jsonObjectRequest as well?(So I don't have to use the if(i==4) statement)
D/Inside Call: [https://i.redd.it/4tb9k1ray1f91.jpg, https://i.redd.it/4tb9k1ray1f91.jpg, https://i.redd.it/4tb9k1ray1f91.jpg, https://i.redd.it/4tb9k1ray1f91.jpg, https://i.redd.it/yftkun6o43f91.gif]
D/Outside Call: []
val data:ArrayList<String> = ArrayList()
for(i in 0 until 5){
val jsonObjectRequest=JsonObjectRequest(
Request.Method.GET,url,null,{
val value:String=it.getString("url")
data.add(value)
if(i==4) {
mAdapter.updateData(data)
}
Log.d("Inside Call",data.toString())
},{}
)
MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest)
}
Log.d("Outside Call",data.toString())
Your assumption of "inside" and "outside" calls is wrong. What you call the inside code is a callback that is, as the name suggests, called when your app has obtained the response to your request. The outside call is executed as soon as you've sent your requests, which is likely to be prior to receiving the responses.
Treat the "inside" code as the "outside" code and you may get closer to your aim. It is the code that will be called when you're "ready" to update your views, in simple terms.
Your i == 4 check is also error prone because it is not guaranteed that you'll get responses to your requests in the same order you've sent them. How you resolve this depends on your specific use case, so it's difficult to offer absolute solutions. You may be able to get away with using a counter that is incremented in the callback each time a response is obtained, and check if the counter has hit its max value (i.e., all responses have been obtained).
Disclaimer: because this question pertains to my work stuff, I cannot get into the actual details. I will try to provide as much information as possible about the issue. Also, I am new to ReactNative. Just started using it since a week and I might be asking something extremely trivial.
Background
There are two apps, let’s call them App-A and App-B. App-A has provisions to start Activities or get some kind of information from App-B. The way the communication contract is established when requesting some information is that App-A will startActivityForResult. The intent will explicitly cite App-B’s Activity (a headless Activity; no UI; every operation is handled in the background) in question. App-B should (ideally) place the request to it’s backend services, get the response and setResult(...) for the activity before calling finish(). I don't have the last part working yet (which is the crux of the problem discussed below)
App-B's structure
App-B makes use of ReactNative for most of the client side stuff. The Network API calls made by App-B are all done in the JavaScript code. When App-A places a “search” request, it passes the “searchTerm” which App-B must send to it’s backend services to get the results/response. Once the response is received, it must be sent back to App-A by “setting the result” using setResult(…) in Activity’s context.
Problem
The problem is passing the response/results from the JavaScript code to the Native code in App-B. Since I need to “keep the request alive" on App-B’s activity, I need to “wait” for the results to be available before setting the result for App-A’s consumption (setResult(...)).
Here is what I tried so far
Make use of EventEmitters: With this, the Java code in App-B will emit an event with an identifier to the ReactNative JavaScript code. However, the emit(…) method takes only two parameters - “event_name/event_identifier” and an Object. The Object can either be primitive datatypes, a Callback or WritableArray/WritableMap (collections defined in ReactNative framework). Since I need to pass the aforementioned “searchTerm” to the JavaScript code to place the network request, I cannot pass the Callback object. If I send a Callback object, then I cannot send the “searchTerm” since I can only pass one parameter. I don't think Promise works here since I still won't be able to send the "searchTerm"
// The "emit" method call below only takes 2 arguments. I can only pass
// either the "searchTerm" or a "Callback"
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)(eventName, someObject);
Define JavaScriptModule: Defining and exposing a "JavaScriptModule" is a way in which the Native/Java code can invoke a method defined in the JavaScript code. This method can be defined to take as many parameters/arguments as seen necessary. The idea with this was to call the JavaScript method from the Native code and pass both the searchTerm and the Callback Object. However, when the passed Arguments are verified, a RuntimeException is thrown since JavaScriptModules can only pass arguments that are of the primitive type or "WritableMap/Array" objects. Callback Objects are not supported. Same with Promise objects; they aren't supported argument types.
// Defined a SearchModule like so (required Module registration is done using ReactPackage
// and setting it in the setPackages() method)
// Followed this: https://stackoverflow.com/questions/38661060/are-there-any-guides-for-implementing-javascriptmodule-in-react-native
// Unfortunately, this throws a RuntimeException since "Callback" is not a
// supported argument
public interface MySearchModule extends JavaScriptModule {
void call(String eventType, String searchQuery, Callback callback);
}
/**
* Different file below
*/
// Arguments.java code snippet in the RN framework
for(int i = 0; i < args.length; ++i) {
Object argument = args[i];
if (argument == null) {
arguments.pushNull();
} else {
Class argumentClass = argument.getClass();
if (argumentClass == Boolean.class) {
arguments.pushBoolean((Boolean)argument);
} else if (argumentClass == Integer.class) {
arguments.pushDouble(((Integer)argument).doubleValue());
} else if (argumentClass == Double.class) {
arguments.pushDouble((Double)argument);
} else if (argumentClass == Float.class) {
arguments.pushDouble(((Float)argument).doubleValue());
} else if (argumentClass == String.class) {
arguments.pushString(argument.toString());
} else if (argumentClass == WritableNativeMap.class) {
arguments.pushMap((WritableNativeMap)argument);
} else {
if (argumentClass != WritableNativeArray.class) {
throw new RuntimeException("Cannot convert argument of type " + argumentClass);
}
arguments.pushArray((WritableNativeArray)argument);
}
}
}
Questions I have
- Is it even possible to achieve something like this in ReactNative framework where the Java code (activity) can call the JavaScript code by passing a bunch of parameters and get a response back?
- Is there anything with respect to EventEmitters/JavaScriptModule using which I could achieve the required outcome?
- Is there any other method for Native/JavaScript communications other than EventEmitters/JavaScriptModule?
- Any other possibilities for this use-case?
I am trying to set up a test for my project - to test that a progress bar is displayed when my app performs a server request.
The code under test uses an AsyncTask to perform the network call.
I have created a blocking server (MockWebServer) to catch and hold the network call - it receives request but doesn't provide a response until i call ".release()". This allows me to verify before the server response occurs.
My logic flows like this:
// Mock server will catch the next network request
BlockingServer blockingServer = createBlockingServer();
// onResume() activity performs network request and shows Progress Spinner
activityTestRule.launchActivity(null);
// onView() waits on UiController.loopUntilIdle() <- Fails here due to timeout.
onView(withId(progressBar)).check(matches(isDisplayed()));
// Tells the server to respond to the network request
blockingServer.release();
onView(withId(progressBar)).check(matches(not(isDisplayed())));
My problem is that because the Code Under Test uses AsyncTask for the server request, Espresso naturally blocks on the verify call (onView()) in order to wait for the AsyncTask to complete before verifying.
What I need is to temporarily stop Espresso idling while waiting for AsyncTask in order to perform the verify while the server is blocking the app logic flow.
(Changing the Code Under Test is not an option)
Can someone help?
So... this is the answer I've arrived at and some working out behind it:
Espresso (specifically calls to onView(), onData(), injectEvent and Actions) uses UiControllerImpl.loopMainThreadUntilIdle() to wait until all "idle-causing" signals are false. It loops over AsyncTask, CompatAsyncTask and something called dynamicIdle to all be idle.
When this method returns the main flow continues.
loopMainThreadUtilIdle() checks an IdleNotifier to check the idle state of each of those three elements. Obviously if you want to stop espresso waiting for AsyncTask the asyncIdle is of particular interest to you.
The IdleNotifier classes are fed into UiControllerImpl at it's construction - this takes place via dagger so you'll need to look at DaggerBaseLayerComponent which uses Providers to grab the construction arguments and pass them into the UiControllerProvider to construct it.
Everything in all of these classes is locked down very tightly. Method and class visibility is usually protected or package-private and final.
The only way I found was to create my own Espresso.java class (onView() and onData()) which used custom DaggerBaseLayerComponent allowing me to use either: My own Providers or My own UiController.
I found however this doesn't solve the whole problem. There is one more mechanism that needs to be coded around - When you're starting activities they use a waitForIdleSync in the Instrumentation class. Usually this is the Runner which is provided in your gradle file. I created my own AndroidJUnitRunner and provided this in gradle to allow me to return from waitForIdleSync on command.
And finally, in startActivitySync in the Instrumentation base class, it uses an array of ActivityWaiter objects to hold up your launchIntent() calls. I couldn't think of a reasonable way of avoiding this so I cheated and created this method in my Runner:
public void clearActivityWaitQueue() {
Object mSync = Whitebox.getInternalState(this, "mSync");
List mWaitingActivities = Whitebox.getInternalState(this, "mWaitingActivities");
if (mSync != null && mWaitingActivities != null) {
mWaitingActivities.clear();
synchronized (mSync) {
mSync.notifyAll();
}
}
}
It uses PowerMock to give me the convenience Whitebox methods to set internal state of Instrumentation:
// Used to give access to Whitebox
androidTestImplementation 'org.powermock:powermock-reflect:1.6.5'
And that's it! Easy right?
(Please tell me it's easier than this and how!!)
I am working with WorkManager Alpha 05.
I'm developing a Service that enqueues task on demand of other applications.
It has two methods:
createTask (Create a new task, given a name and a set of data, it returns and ID)
checkTaskStatus (The application asks the services given a ID, the status of the task)
The communication is done via bound services using messages. That means both client and services has the correct implementations to communicate information.
Method 1 is working fine.
I have problems with method 2.
WorkManager.getInstance().getStatusById(taskID)
.observe(LifecycleOwner, Observer {
status -> if (status !=null){
val myResult = status.state.toString()
statusString = myResult
Log.d("Task Status",myResult)
}
})
The observer is logging the status correctly, but I can't send back that message to the client. Is there a way to check the status in a sync way?
I don't really need to have the task attached to a LiveData.
Seems like SynchronousWorkManager was removed on October 11:
Removed WorkManager.synchronous() and WorkContinuation.synchronous() and all related methods. Added ListenableFuture as the return type of many methods in the API. This is a breaking API change.
How to use ListenableFuture:
You can now synchronously get and observe by using ListenableFutures. For example, WorkManager.enqueue() used to return void; it now returns a ListenableFuture. You can call ListenableFuture.addListener(Runnable, Executor) or ListenableFuture.get() to run code once the operation is complete.
More info can be found here.
The WorkManager instance has a synchronous method which returns the SynchronousWorkManager, This will give you a set of methods to perform synchronous operations. Take into account that this is meant to be used in a background thread.
I'm trying to figure out how to use Loaders in Android 3.0 but can't seem to get it to work. The docs only describe using CursorLoader but I'm using AsyncTaskLoader.
From the docs it seems that you should only need to implement AsyncTaskLoader.loadInBackground() but it never gets called after getLoaderManager().initLoader() and then creating the loader in the callback.
I can see debug messages saying Created new loader LoaderInfo{4040a828 #0 : ArticleDataLoader{4036b350}} so it seems like it is created successfully.
Is it possible that loaders are currently broken in the SDK or is there some method you need to call after creating the loader? (they haven't done that in the CursorLoader example).
EDIT: Seems like calling forceLoad() on the Loader returned from initLoader() starts the loading at least but this means you can't handle rotations correctly :(
Dianne Hackborn replied on the bug tracker and referred us to the static library implementation. CursorLoader is doing forceLoad() which is why it is working.
See my attached class for a class which handles this for you in most simple cases at the bug tracker: http://code.google.com/p/android/issues/detail?id=14944
You need to override the onStartLoading() method. Look at the example on the developer website.
/**
* Handles a request to start the Loader.
*/
#Override protected void onStartLoading() {
if (mApps != null) {
// If we currently have a result available, deliver it
// immediately.
deliverResult(mApps);
}
// Start watching for changes in the app data.
if (mPackageObserver == null) {
mPackageObserver = new PackageIntentReceiver(this);
}
// Has something interesting in the configuration changed since we
// last built the app list?
boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());
if (takeContentChanged() || mApps == null || configChange) {
// If the data has changed since the last time it was loaded
// or is not currently available, start a load.
forceLoad();
}
}
Alex;
Did you try to validate if the onLoadInBackground () gets even called?
onLoadInBackground (): Called on a worker thread to perform the actual load. Implementations should not deliver the result directly, but should return them from this method, which will eventually end up calling deliverResult(D) on the UI thread. If implementations need to process the results on the UI thread they may override deliverResult(D) and do so there.