I'm trying to make what I thought would be a simple call to a google app engine project.
From a unit test, the call works fine if I access the api directly. I can't do this however, since the call is blocking, so it has to be run from an async task.
What seems to happen is:
A unit test inheriting from ActivityInstrumentationTestCase2 sets up the activity and populates some values
Then I click the button to make the request:
final Button mBet = (Button) mActivity.findViewById(R.id.mybutton);
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
mName.setText("blah");
mBet.performClick();
}
});
Then this calls the underlying code to start an async task:
public void bet() throws IOException, InterruptedException {
mMakeTask = new AsyncTask<Void, Void, Model>() {
#Override
protected void onPreExecute() {
super.onPreExecute();
mBetWait = new ProgressDialog(getActivity());
mBetWait.setCancelable(true);
mBetWait.show();
}
#Override
protected Model doInBackground(Void... voids) {
try {
mModel = mController.makeBet(mModel.getBetName(), mModel.getDescription(), mModel.getAnswer(), mModel.getAgainst());
} catch (IOException e) {
e.printStackTrace();
}
return mModel;
}
#Override
protected void onPostExecute(BetModel model) {
super.onPostExecute(model);
Context context = getActivity();
Intent intent = new Intent(context, OtherActivity.class);
intent.putExtra("bet", mModel);
mBetWait.dismiss();
context.startActivity(intent);
}
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
mMakeTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
mMakeTask.execute();
}
}
And I forgot to add... when controller is called, it boils down to an appengine call:
public BetInfo makeBet(BetRequest info) throws IOException {
Betrequestendpoint.Builder endpointBuilder = new Betrequestendpoint.Builder(
AndroidHttp.newCompatibleTransport(),
new JacksonFactory(),
new HttpRequestInitializer() {
public void initialize(HttpRequest httpRequest) { }
});
Betrequestendpoint endpoint = CloudEndpointUtils.updateBuilder(
endpointBuilder).build();
BetRequest result = endpoint.insertBetRequest(info).execute();
BetInfo resultInfo = new BetInfo();
resultInfo.setName(result.getName());
resultInfo.setDescription(result.getDescription());
resultInfo.setId(result.getId());
return resultInfo;
}
And to make this more odd... the request call appears to be blocked from the client with the stack trace:
"<13> AsyncTask #2"#831,912,607,944 in group "main": RUNNING
toUpperCase():3266, Character {java.lang}
toUpperCase():3251, Character {java.lang}
toUpperCase():162, CaseMapper {java.lang}
toUpperCase():1548, String {java.lang}
initServiceInfo():152, Services {org.apache.harmony.security.fortress}
getCacheVersion():211, Services {org.apache.harmony.security.fortress}
getInstance():137, Engine {org.apache.harmony.security.fortress}
getInstance():77, KeyManagerFactory {javax.net.ssl}
createDefaultKeyManager():362, SSLParametersImpl {com.android.org.conscrypt}
getDefaultKeyManager():355, SSLParametersImpl {com.android.org.conscrypt}
<init>():111, SSLParametersImpl {com.android.org.conscrypt}
getDefault():146, SSLParametersImpl {com.android.org.conscrypt}
<init>():34, OpenSSLSocketFactoryImpl {com.android.org.conscrypt}
newInstanceImpl():-1, Class {java.lang}
newInstance():1208, Class {java.lang}
getDefault():56, SSLSocketFactory {javax.net.ssl}
<clinit>():114, HttpsURLConnection$NoPreloadHolder {javax.net.ssl}
getDefaultSSLSocketFactory():163, HttpsURLConnection {javax.net.ssl}
copyWithDefaults():363, OkHttpClient {com.android.okhttp}
open():345, OkHttpClient {com.android.okhttp}
open():340, OkHttpClient {com.android.okhttp}
openConnection():28, HttpHandler {com.android.okhttp}
openConnection():479, URL {java.net}
buildRequest():133, NetHttpTransport {com.google.api.client.http.javanet}
buildRequest():68, NetHttpTransport {com.google.api.client.http.javanet}
execute():858, HttpRequest {com.google.api.client.http}
executeUnparsed():410, AbstractGoogleClientRequest {com.google.api.client.googleapis.services}
executeUnparsed():343, AbstractGoogleClientRequest {com.google.api.client.googleapis.services}
execute():460, AbstractGoogleClientRequest {com.google.api.client.googleapis.services}
makeBet():36, BetController {com.chillypixel.youwereright.controller}
makeBet():36, BetApiController {com.chillypixel.youwereright.controller}
doInBackground():129, BetFragment$1 {com.chillypixel.youwereright.bet}
doInBackground():117, BetFragment$1 {com.chillypixel.youwereright.bet}
call():288, AsyncTask$2 {android.os}
run():237, FutureTask {java.util.concurrent}
runWorker():1112, ThreadPoolExecutor {java.util.concurrent}
run():587, ThreadPoolExecutor$Worker {java.util.concurrent}
run():841, Thread {java.lang}
What seems to happen, is I run the test, and it gets to where the progress dialog pops up.
If I then manually interact with the app in the emulator (and hit back or something) the progress dialog goes away and the call completes - then the test passes.
If I just leave it alone though, it appears that the progress dialog just happily spins forever.
Any help would be greatly appreciated.
Related
After running this,
App run.
TestMethod1() executed.
App closed.
TestMethod2() executed.
But I don't want the app to be closed (Step3). I want TestMethod2() to be executed after TestMethod1() is done:
App run.
TestMethod1() execute.
TestMethod2() execute.
App close.
--> I also tried with AddAdditionalCapability("NoReset, true") and AddAdditionalCapability("FullReset, False"), which didn't work.
=> I am using Appium.WebDriver(4.3.1) and C#.
[TestClass]
public class Walkthrough
{
private string _appPath = #"PathToMyApp\MyApp.apk";
private AppiumDriver<AndroidElement> _driver;
[TestInitialize]
public void Setup()
{
var appiumOption = new AppiumOptions();
appiumOption.AddAdditionalCapability(MobileCapabilityType.App, _appPath);
appiumOption.AddAdditionalCapability(MobileCapabilityType.PlatformName, "Android");
appiumOption.AddAdditionalCapability(MobileCapabilityType.DeviceName, "Pixel 4");
appiumOption.AddAdditionalCapability(MobileCapabilityType.PlatformVersion, "12");
appiumOption.AddAdditionalCapability(MobileCapabilityType.Udid, "99171FFAZ000GE");
appiumOption.AddAdditionalCapability(MobileCapabilityType.NoReset, true);
_driver= new AndroidDriver<AndroidElement>(new Uri("http://127.0.0.1:4723/wd/hub"), appiumOption);
}
[TestCleanup]
public void TestCleanup()
{
_driver.CloseApp();
}
[TestMethod]
public void TestMethod1()
{
_driver.FindElement(By.ID("ELEMENT1")).Click();
}
[TestMethod]
public void TestMethod2()
{
_driver.FindElement(By.ID("ELEMENT2")).Click();
}
}
I finally solved the problem by adding [ClassInitialize] and TestContext in Setup method.
Make sure your method is "Static".
[ClassInitialize]
public static void Setup(TestContext testContext)
{
{
var appiumOption = new AppiumOptions();
appiumOption.AddAdditionalCapability(MobileCapabilityType.App, _appPath);
appiumOption.AddAdditionalCapability(MobileCapabilityType.PlatformName, "Android");
appiumOption.AddAdditionalCapability(MobileCapabilityType.DeviceName, "Pixel 4");
appiumOption.AddAdditionalCapability(MobileCapabilityType.PlatformVersion, "12");
appiumOption.AddAdditionalCapability(MobileCapabilityType.Udid, "99171FFAZ000GE");
appiumOption.AddAdditionalCapability(MobileCapabilityType.NoReset, true);
_driver= new AndroidDriver<AndroidElement>(new Uri("http://127.0.0.1:4723/wd/hub"), appiumOption);
}
}
In my android application I have a screen where I have 3 spinners that need to be
filled from APIs call.
static List<TripCode> tripCodeList = new ArrayList<>();
static List<Fleet> truckList = new ArrayList<>();
static List<Trailer> trailerList = new ArrayList<>();
And I don't want to inflate the layout unless I get the response from all the 3 different API calls so this is what I'm doing
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
if (MyApplication.isConnected()) {
getTripCodes();
} else {
Toast.makeText(this, "No internet Connection", Toast.LENGTH_LONG).show();
setContentView(R.layout.no_internet_connection);
}
}
Basically , I removed setContentView(R.layout.activity_create_trip);
from onCreate() And I called getTripCodes()
here's the code for getTripCodes()
public void getTripCodes() {
MyApplication.showProgressDialog(getString(R.string.please_wait), this);
IMyAPI iMyAPI = MyApplication.getIMyAPI();
Call<List<TripCode>> call = iMyAPI.getTripCodes();
call.enqueue(new Callback<List<TripCode>>() {
#Override
public void onResponse(Call<List<TripCode>> call, Response<List<TripCode>> response) {
if (response.isSuccessful() && response.body() != null) {
tripCodeList = response.body();
Log.d("test", "getTripCodes success = " + tripCodeList.size());
getTrucks();
} else {
MyApplication.dismissProgressDialog();
}
}
#Override
public void onFailure(Call<List<TripCode>> call, Throwable t) {
MyApplication.dismissProgressDialog();
}
});
}
So in the success of the call I'm calling the other function getTrucks() which also get result from API and in the success it will call getTrailers()
But I think it's a waste of time, because I can call the three function all together in parallel, and then check if all the list are filled or not.
But I don't know how to do it. How can I check if all the calls are success? And if one of them has failed, how will I know which one exactly failed?
I Believe for your problem you can easily use Retrofit 2.6.0 which has coroutine support and you can declare all the function's as suspended function's and dispatch them with async/launch dispatcher and if you want to wait for some result in some case use await() to wait for the result.
And use RxJava/liveData for responsive UI
sample code for you will look like
//maybe from Activity for ViewModel you can use ViewModelScope
GlobalScope.launch{
result1= async{ getTripCodes() }
result2= async{ getTrucks() }
result3= async{ getTrailers() }
doSomethingWithTripCodes(result1.await())
doSomethingWIthTrucks(result2.await())
doSomethingTrailers(result3.await())
}
Reference:
post1
I have a simple Android app. The main activity looks for a file on the internal device storage. What happens after that depends on whether the file exists or not. This sequence works just fine as the whole execution is synchronous.
If I now use Google Drive API, all file access are asynchronous and the main activity goes on without waiting for the result of my search...
I found no way to 'force' a synchronous behavior. Synchronous calls of Google API are not allowed in the UI thread.
Moreover, performing such calls in an AsyncTask leads to the same problem...
Any idea on how to handle such scenarios ?
Regards,
Laurent
------------------------- EDIT ----------------------------------
I tried several options but they all lead to the same result.
Both the main activity and the asynctask used to look for my file get blocked.
Any idea why and what to do ?
Main activity code :
if (!TermsOfUseAgreementHandler.mTermsOfUseAgreed) {
Log.i(mTag, "Terms of use agreement needs to be checked");
mCountDownLatch = new CountDownLatch(1);
TermsOfUseAgreementSearchTask searchAgreementTask = new TermsOfUseAgreementHandler().new TermsOfUseAgreementSearchTask();
// Start the async task to look for agreement file using parallel execution
searchAgreementTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
try {
// Block the main thread so that the task gets a chance to perform its Google Drive access...
mCountDownLatch.await(); // MAIN ACTIVITY HANGS HERE !
} catch (InterruptedException e) {
Log.e(mTag, "Failed to look for user agreement file", e);
}
// The async task should have finished (and decreased mCountDownLatch so that await returns)
// At this moment, the result should be available for the main activity in mTermsOfUseAgreed...
if (!TermsOfUseAgreementHandler.mTermsOfUseAgreed)
TermsOfUseAgreementHandler.checkTermsOfUse(this);
}
AsyncTask code :
protected Boolean doInBackground(Void... params) {
Log.i(MainActivity.mTag, "Looking for user agreement file");
// Look for the agreement file with synchronous Google Drive API access
Query lookForAgreement = new Query.Builder().addFilter(Filters.eq(SearchableField.TITLE, mTermsOfUseAgreementFile)).build();
MetadataBuffer result = Drive.DriveApi.getAppFolder(MainActivity.mGoogleApiClient).queryChildren(MainActivity.mGoogleApiClient, lookForAgreement).await().getMetadataBuffer(); // ASYNCTASK HANGS HERE !
mTermsOfUseAgreed = result != null
&& result.getCount() != 0
&& result.get(0) != null
&& result.get(0).getTitle() != null
&& result.get(0).getTitle().equals(mTermsOfUseAgreementFile);
// Now that the result is available, decrease the countDownLatch so that the main activity may continue...
MainActivity.mCountDownLatch.countDown();
return mTermsOfUseAgreed;
}
------------------------- EDIT 2 ----------------------------------
I also tried to move the connection to the drive API from the main activity to the asynctask as shown in a drive API example link.
I still get the same result but the problem is now the connection because the associated callback listener is never called...
public class TermsOfUseAgreementSearchTask extends AsyncTask<Void, Void, Boolean> {
private GoogleApiClient mClient = null;
final private CountDownLatch latch = new CountDownLatch(1);
public TermsOfUseAgreementSearchTask(Context context) {
GoogleApiClient.Builder builder = new GoogleApiClient.Builder(context)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addScope(Drive.SCOPE_APPFOLDER);
mClient = builder.build();
mClient.registerConnectionCallbacks(new ConnectionCallbacks() {
#Override
public void onConnected(Bundle arg0) {
Log.i(MainActivity.mTag, "Connected");
latch.countDown();
}
#Override
public void onConnectionSuspended(int arg0) {}
});
mClient.registerConnectionFailedListener(new OnConnectionFailedListener() {
#Override
public void onConnectionFailed(ConnectionResult arg0) {
Log.i(MainActivity.mTag, "Connection failed");
latch.countDown();
}
});
}
#Override
protected Boolean doInBackground(Void... params) {
Log.i(MainActivity.mTag, "Connect to drive API");
mClient.connect();
try {
Log.i(MainActivity.mTag, "Wait for connection");
latch.await(); // ASYNC TASK HANGS HERE !
} catch (InterruptedException e) {
return false;
}
if (!mClient.isConnected()) {
return false;
}
try {
Log.i(MainActivity.mTag, "Looking for user agreement file");
// Look for the agreement file with synchronous Google Drive API access
Query lookForAgreement = new Query.Builder().addFilter(Filters.eq(SearchableField.TITLE, mTermsOfUseAgreementFile)).build();
MetadataBuffer result = Drive.DriveApi.getAppFolder(mClient).queryChildren(mClient, lookForAgreement).await().getMetadataBuffer();
mTermsOfUseAgreed = result != null
&& result.getCount() != 0
&& result.get(0) != null
&& result.get(0).getTitle() != null
&& result.get(0).getTitle().equals(mTermsOfUseAgreementFile);
// Now that the result is available, decrease the countDownLatch so that the main activity may continue...
//MainActivity.mCountDownLatch.countDown();
return mTermsOfUseAgreed;
} finally {
mClient.disconnect();
}
}
}
Ever get those moments where you stare at a piece of code for an hour and still can't come up with an answer? Yeah that's me now.
I'm working on a final project for class and I can't get this one piece of code to work. It is absolutely crucial that it works, or else it defeats the purpose of the program. I even asked my professor for help... and he doesn't know how to help me solve the issue. I posted a similar problem a day ago but I want to re-ask in a different way to see if it helps (Sorry if its a re-post, I have no other source for help :/).
My problem is that I need to access array elements on my MainActivity after its populated inside an AsyncTask class. The array is defined globally but as soon as I try to access it's element or size, it crashes. I need to be able to call this array outside of AsyncTask.
I've searched for hours and tried "returning" the array from AsyncTask but it crashes as well.
Here is my code (I've included comments as to where it crashes):
public class PostsActivity extends Activity {
public static GlobalRates[] gr;
TextView view;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_posts);
view = (TextView) findViewById(R.id.textView1);
BitRateFetcher br = new BitRateFetcher();
br.execute();
// !!! Line below crashes !!!
Log.i("BitRateFetcher", "Size from onCreate: " + gr.length);
}
private class BitRateFetcher extends AsyncTask<Void, Void, GlobalRates[]> {
private static final String TAG = "BitRateFetcher";
public String BIT_PAY_SERVER = "https://bitpay.com/api/rates";
private ProgressDialog dialog;
GlobalRates[] test;
#Override
protected void onPreExecute() {
super.onPreExecute();
dialog = new ProgressDialog(PostsActivity.this);
dialog.setMessage("Please Wait... Downloading Information");
dialog.show();
}
#Override
protected GlobalRates[] doInBackground(Void... params) {
try {
// Create an HTTP client
HttpClient client = new DefaultHttpClient();
HttpGet getBitRates = new HttpGet(BIT_PAY_SERVER);
// Perform the request and check the status code
HttpResponse bitRatesResponse = client.execute(getBitRates);
StatusLine bitRatesStatus = bitRatesResponse.getStatusLine();
if (bitRatesStatus.getStatusCode() == 200) {
HttpEntity entity = bitRatesResponse.getEntity();
InputStream content = entity.getContent();
try {
// Read the server response and attempt to parse it as
// JSON
Reader reader = new InputStreamReader(content);
Gson gson = new Gson();
test = gson.fromJson(reader, GlobalRates[].class);
content.close();
entity.consumeContent();
} catch (Exception ex) {
Log.e(TAG, "Failed to parse JSON due to: " + ex);
failedLoadingPosts();
}
} else {
Log.e(TAG, "Server responded with status code: "
+ bitRatesStatus.getStatusCode());
failedLoadingPosts();
}
} catch (Exception ex) {
Log.e(TAG, "Failed to send HTTP POST request due to: " + ex);
failedLoadingPosts();
}
return test;
}
#Override
protected void onPostExecute(GlobalRates[] test) {
Log.i(TAG, "Test Size: " + test.length); // Returns 158
gr = test;
Log.i(TAG, "Gr Size: " + gr.length); // Returns 158
if (dialog.isShowing()) {
dialog.dismiss();
}
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.posts, menu);
return true;
}
private void failedLoadingPosts() {
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(PostsActivity.this,
"Failed to load Posts. Have a look at LogCat.",
Toast.LENGTH_SHORT).show();
}
});
}
}
Here is the error Log:
04-21 20:30:01.954: E/AndroidRuntime(32595): FATAL EXCEPTION: main
04-21 20:30:01.954: E/AndroidRuntime(32595): Process: com.example.postsactivity, PID: 32595
04-21 20:30:01.954: E/AndroidRuntime(32595): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.postsactivity/com.example.postsactivity.PostsActivity}: java.lang.NullPointerException
My class partner and I are unsure as to how we can call the global array (gr) properly. I understand that onCreate is not going to wait for AsyncTask to finish. What can we do to get this to work? Thanks for the help and understanding. You guys can prove more useful than my professor...
the problem is that you are accessing array of globalrates before it was initialized in your postExecute(). There are a lot of way to do this one of which is creating an interface or creating callbacks that waits for globalrate to be initialized after ur background thread is done.
Create a Interface
public interface SampleInterface {
void globalResultBackground(GlobalRates[] gr);
}
implements the interface to ur activity
public class PostsActivity extends Activity implements SampleInterface
pass the interface to your asynctask
//in the activity
BitRateFetcher br = new BitRateFetcher(this);
br.execute();
//in the asynctask class
SampleInterface si;
public BitRateFetcher(SampleInterface si){
this.si = si;
}
pass the result of the background thread to ur interface method
#Override
protected void onPostExecute(GlobalRates[] test) {
si.globalResultBackground(test);
Log.i(TAG, "Test Size: " + test.length); // Returns 158
gr = test;
Log.i(TAG, "Gr Size: " + gr.length); // Returns 158
if (dialog.isShowing()) {
dialog.dismiss();
}
}
in the activity where the you implemented the interface and generated the globalResultBackground method
#Override
public void globalResultBackground(GlobalRates[] gr) {
//you can freely access the globalrates here because this is called when the
//background thread is done
Log.i("BitRateFetcher", "Size from onCreate: " + gr.length);
}
Pass in your activity to the AsyncTask and directly access its gr in your onPostExecute
Sample code:
private class BitRateFetcher extends AsyncTask {
PostsActivity activity;
...
public BitRateFetcher(PostsActivity activity){
this.activity= activity;
}
...
#Override
protected void onPostExecute(GlobalRates[] test) {
activity.gr = ...
}
}
In PostsActivity,
BitRateFetcher br = new BitRateFetcher(this);
Take care to attach and reAttach the activity to handle scenarios such as device rotations.
EDIT: Just noticed you've got a static, so you don't even need to pass in the activity. The same principle applies though.
GlobalRates[] gr is public and static, so you dont really new GlobalRates[] test
simply replace every instance of test with gr:
test = gson.fromJson(reader, GlobalRates[].class);
to
gr = gson.fromJson(reader, GlobalRates[].class);
if you need to access gr in Oncreate then move that logic to postExecute
Log.i("BitRateFetcher", "Size from onCreate: " + gr.length);
I've run into this error before, but thought it was some mistake by the strict mode system. However, it apparently was right as I sadly found out now. :(
My programm is made of one Activity and loads of Fragments. I have a NetworkWorker fragment, which starts URL requests like this:
public void startURLRequest(Fragment target, String url, String message)
{
if (asyncTask != null) asyncTask.cancel(true);
asyncTask = new FragmentHttpHelper(url, message, target);
asyncTask.doInBackground();
return;
}
FragmentHttpHelper is a custom inner class derived from AsyncTask:
private class FragmentHttpHelper extends AsyncTask<Void, Void, String>
{
//...
#Override
protected String doInBackground(Void... params)
{
if (CheckInternet())
{
try
{
URL myURL = new URL(url);
httpClient = new DefaultHttpClient();
if (this.message == null)
{
httpRequest = new HttpGet(myURL.toExternalForm());
}
else
{
httpRequest = new HttpPost(myURL.toExternalForm());
HttpEntity myEntity = new StringEntity(message, "UTF-8");
((HttpPost) httpRequest).setEntity(myEntity);
}
// and so on...
}
//catches
finally
{
// auf jeden Fall Verbindung beenden
if (httpRequest != null) httpRequest.abort();
// if (httpClient != null) httpClient.close();
}
}
else
{
showDialog(getString(R.string.net_notify_no_network), target);
}
//...
}
/**
* gets called after AsyncTask has finished
*/
protected void onPostExecute(String result)
{
if (target == null)
{
((NetworkWorkerListener) getActivity()).onDownloadHasFinished((!result.contentEquals(ERROR)), result);
}
else
{
((NetworkWorkerListener) target).onDownloadHasFinished((!result.contentEquals(ERROR)), result);
}
}
}
NetworkWorkerListener is just an interface for a callback on the Fragment which started the URL request. This class has always worked fine when I used it in my 2.2 app. I would derive it in my Activities then.
Now, if a menu item is selected, another worker Fragment starts the URL request via the above method and opens a loading dialog:
FragmentManager fragmentManager = getFragmentManager();
NetworkWorker network = (NetworkWorker) fragmentManager.findFragmentByTag(TabletMain.NETWORK);
if (network == null) return WorkerFeedback.NO_NETWORK_WORKER;
myDialog = LoadingDialog.createInstance(getString(R.string.notify_download), this);
myDialog.show(fragmentManager, TabletMain.ONETIME);
network.startURLRequest(this, someurl, null);
At least that's what supposed to happen.
Instead, when I click the menu item, my app freezes and no loading dialog is shown until. Next happening is the reaction to the end of the download (or, in my case an error message, as I am sending nonsense strings). Meaning onPostExecute() was reached.
I feel really stuck now - is it not possible to use AsyncTask with Fragments? Or did I do something wrong?
Thanks for your help,
jellyfish
Don't call doInBackground directly, call execute instead (on the async task)