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?
Related
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.
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 am testing [Using UI Automator] a particular task, where a thread is started when the application class is created. [onCreate()].
Now, I have mocked the entire thread which makes a http call to series of tasks that happen inside the app. [mockedThread]
Now, I use a variable to access this object when TEST=true and run in the normal mode where a http call is made when TEST=false. I make TEST=true only inside the test methods.
Right now the code works in this way -
onCreate() {
if TEST == true {
use mock object and update it manually.
-- mockedObject
} else if TEST == false {
use real object and update it using http calls.
-- real Object
}
}
Now I'm trying to use the mocked object without this if -- else loop of testing = TRUE or FALSE. Is there any was we can do this with mockito or any other way... like dagger2 or something ?
[PS: Please don't advise on Powermock as these are not static object]
I am trying to mock it like this but doesn't work :
testMock() {
try {
AndroidApplication application = Mockito.mock(AndroidApplication.class);
context.startActivity(intent);
mDevice.wait(Until.hasObject(By.pkg(TARGET_PACKAGE).depth(0)), Constants.LAUNCH_TIMEOUT);
Mockito.when(application.getThreadObject()).thenReturn(mockObject);
} catch(MockException m){
m.printStackTracke();
}
}
Error I get it - Actually, there were zero interactions with this mock.
I come from an iOS background and I'm new to Android.
Is there an efficient and fast way to make the same network API call but with different parameters each time where the parameters are stored in an array. I would only want to return when all the network API calls have completed, but I don't want any of the api calls in the loop to block other api calls in the loop.
I basically want the equivalent of this Swift code. Basically the function below won't return until all network calls getData has either succeeded or failed. How would I accomplish the same thing below in Android?
func getDataForParameters(array: NSArray) {
let group = dispatch_group_create()
for (var i = 0; i < array!.count(); i++) {
let param = array![i]
dispatch_group_enter(group)
getData(param, success: {
() in
dispatch_group_leave(group)
}, failure: {
() in
dispatch_group_leave(group)
})
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
}
You have many ways to achieve this.
You can use Thread.join() in case you are using threads
you can use 3rd party libraries like RxJava.
you can write your own event dispatcher here is an ugly example
This answer also covers your question Callable and Future
If the network calls in the loop shouldn't block other network call then you should make the network calls asynchronously.
You can use google's volley network library to make the network calls and they execute asynchronously. Follow the below link for volley
https://developer.android.com/training/volley/index.html.
if you can implement a counter which increments on either success or failure call back you can use that variable to determine whento return back to your calling method.
Since the network calls are being made asynchronously you need to write a callback interface which should be triggered once your counter condition is met so that it will send a callback to the called method. you can find lot of examples on how use callback mechanism in Android. Callback functions are like Delegate functions in IOS.
I Hope this helps.