How to find out what AsyncTask was running? - java

In one of my Activities I do have up to six different AsyncTasks that may run on different events. All AsyncTasks handle orientation changes gracefully (at least I hope so). In onRetainNonConfigurationInstance() I do return the AsyncTask object of a currently running AsyncTask. Later, during onCreate(), I need to find out what AsyncTask object is returned from getLastNonConfigurationInstance().
I use the Activity context in all onPostExecute() methods to get the new activity (if there is a new one). I found this concept here on StackOverflow and did modify it a little bit because I don't like that "Trash running tasks on orientation change" paradigma. Hope this concept is correct.
In the code shown below you'll find two different AsyncTasks. During onRetainNonConfigurationInstance() I will return the currently running AsyncTask. My problem is within onCreate(). How to find out what object is returned? What AsyncTask was running when orientation change bumped in?
Both AsyncTasks are different in many areas (not shown here) so I didn't use an own extended AsyncTask base object.
Many thanks in advance.
public class MyActivity extends ListActivity {
// First AsyncTask
private class MyLoadAsyncTask extends AsyncTask<Void, Void, Cursor> {
private MyActivity context;
private MyProgressDialog dialog;
public MyLoadAsyncTask(MyActivity context) {
super();
this.context = context;
}
#Override
protected Cursor doInBackground(Void... voids) {
return MyApplication.getSqliteOpenHelper().fetchSoomething();
}
#Override
protected void onPostExecute(Cursor cursor) {
if (dialog != null) {
dialog.dismiss();
dialog = null;
}
if (cursor != null) {
context.startManagingCursor(cursor);
context.adapter = new InternetradiosAdapter(context,
R.layout.row,
cursor,
new String[] { "text1",
"text2" },
new int[] { R.id.row_text1,
R.id.row_text2 } );
if (context.adapter != null) {
context.setListAdapter(context.adapter);
}
}
context.loadTask = null;
}
#Override
protected void onPreExecute () {
dialog = MyProgressDialog.show(context, null, null, true, false);
}
}
private class MyDeleteAsyncTask extends AsyncTask<Void, Void, Boolean> {
// Second AsyncTask
private MyActivity context;
private MyProgressDialog dialog;
private long id;
public MyDeleteAsyncTask(MyActivity context, long id) {
super();
this.context = context;
this.id = id;
}
#Override
protected Boolean doInBackground(Void... voids) {
return MyApplication.getSqliteOpenHelper().deleteSomething(id);
}
#Override
protected void onPostExecute(Boolean result) {
if (dialog != null) {
dialog.dismiss();
dialog = null;
}
if (result) {
context.doRefresh();
}
context.deleteTask = null;
}
#Override
protected void onPreExecute () {
dialog = MyProgressDialog.show(context, null, null, true, false);
}
}
private MyDeleteAsyncTask deleteTask;
private MyLoadAsyncTask loadTask;
#Override
public void onCreate(Bundle bundle) {
// ...
// What Task is returned by getLastNonConfigurationInstance()?
// ...
// xxxTask = (MyXxxAsyncTask) getLastNonConfigurationInstance();
// ...
if (deleteTask != null) {
deleteTask.context = this;
deleteTask.dialog = MyProgressDialog.show(this, null, null, true, false);
} else if (loadTask != null) {
loadTask.context = this;
loadTask.dialog = MyProgressDialog.show(this, null, null, true, false);
} else {
loadTask = new MyLoadAsyncTask(this);
loadTask.execute();
}
}
#Override
public Object onRetainNonConfigurationInstance() {
if (deleteTask != null) {
if (deleteTask.dialog != null) {
deleteTask.dialog.dismiss();
deleteTask.dialog = null;
return deleteTask;
}
} else if (loadTask != null) {
if (loadTask.dialog != null) {
loadTask.dialog.dismiss();
loadTask.dialog = null;
return loadTask;
}
return null;
}
}

In your onCreate() add:
Object savedInstance = getLastNonConfigurationInstance();
if(savedInstance instanceof MyDeleteAsyncTask){
//it's a MyDeleteAsyncTask
}else if(savedInstance instanceof MyLoadAsyncTask){
//it's a MyLoadAsyncTask
}

I've found that the best way to deal with activities that have multiple running AsyncTasks inside of them is to actually return an object that contains all of the running ones and automatically reinitialize all of their contexts in onCreate(). E.g.
private class AsyncTaskList() {
List<ActivityTask> tasks; //interface all of your AsyncTasks implement
public void addTask() { /* add to list*/ }
public void completeTask { /* remove from list */ }
public void attachContext(Activity activity) {
for ( ActivityTask task : tasks) {
//You can also check the type here and do specific initialization for each AsyncTask
task.attachContext(activity);
}
}
}
public void onCreate(Bundle bundle) {
AsyncTaskList taskList = (AsyncTaskList) getLastNonConfigurationInstance();
if (taskList != null) {
taskList.attachContext(this);
}
...
}
Now you just need to add and remove the tasks when they start/finish.

Related

How should I start a new Activity, after the application is done fetching and passing the fetched data to it?

In my app, I use a Contacts database and display those contacts using a RecyclerView . When I click on a contact, I want to fetch its data from the tables in the DB, and load them in a new Activity, ContactCard. I have an AsyncTask() which fetches the PhoneNumber objects that match the selected contactId, but I will also need to retrieve the Address and Email objects from the other tables.
I would like to be able to start the activity after all the relevant data is fetched, and I tried doing this in the activity with the Contacts RecyclerView, but the application crashes as the data has not been fetched yet.
I can call the new activity using an intent, but how can I ensure data from different tables is fetched first, before I start the new activity (which effectively displays this data)?
Some of my code:
public class PhoneNumberRepository {
private WorksideDatabase worksideDatabase;
private List<PhoneNumber> returnedNumbers;
private Context mContext;
public PhoneNumberRepository(Context context) {
String DB_NAME = "workside_database";
worksideDatabase = Room.databaseBuilder(context, WorksideDatabase.class, DB_NAME).build();
mContext = context;
}
public List<PhoneNumber> fetchPhoneNumbers(final int id) {
new AsyncTask<Integer, Void, List<PhoneNumber>>() {
#Override
protected List<PhoneNumber> doInBackground(Integer... ids) {
returnedNumbers = worksideDatabase.phoneNumberDao().getPhoneNumbersById(id);
System.out.println(returnedNumbers);
for (PhoneNumber pn : returnedNumbers) {
System.out.println("Number: " + pn.getPhoneNumber());
}
return returnedNumbers;
}
// This runs in UI when background thread finishes
#Override
protected void onPostExecute(List<PhoneNumber> result) {
super.onPostExecute(result);
System.out.println("Entered onPostExecute of fetchPhoneNumbers");
// for (PhoneNumber pn : result) {
// Toast.makeText(mContext, pn + "", Toast.LENGTH_SHORT).show();
// }
}
}.execute();
return returnedNumbers;
}
public List<PhoneNumber> getPhoneNumbers(int id) {
return fetchPhoneNumbers(id);
}
}
ContactsFragment:
adapter.setOnItemClickListener(
contact -> {
Intent viewContact = new Intent(getActivity(), WorksideContactCard.class);
viewContact.putExtra(WORKSIDE_CONTACT, contact);
PhoneNumberRepository phoneNumberRepository =
new PhoneNumberRepository(getActivity().getApplicationContext());
List<PhoneNumber> phoneNumberList;
phoneNumberList = phoneNumberRepository.getPhoneNumbers(contact.getId());
ArrayList<PhoneNumber> arrlistPhoneNumbers =
new ArrayList<>(phoneNumberList);
viewContact.putParcelableArrayListExtra(
WORKSIDE_CONTACT_PHONE_NO, arrlistPhoneNumbers);
startActivity(viewContact);
}
You can do this when you click on an item start the asyntask like this
adapter.setOnItemClickListener(
contact -> {
PhoneNumberRepository phoneNumberRepository =
new PhoneNumberRepository(getActivity().getApplicationContext());
List<PhoneNumber> phoneNumberList;
phoneNumberRepository.getPhoneNumbers(contact.getId());
}
and change your PhoneNumberRepository to this class
public class PhoneNumberRepository {
private WorksideDatabase worksideDatabase;
private List<PhoneNumber> returnedNumbers;
private Context mContext;
public PhoneNumberRepository(Context context) {
String DB_NAME = "workside_database";
worksideDatabase = Room.databaseBuilder(context, WorksideDatabase.class, DB_NAME).build();
mContext = context;
}
public void fetchPhoneNumbers(final int id) {
new AsyncTask<Integer, Void, List<PhoneNumber>>() {
#Override
protected List<PhoneNumber> doInBackground(Integer... ids) {
returnedNumbers = worksideDatabase.phoneNumberDao().getPhoneNumbersById(id);
System.out.println(returnedNumbers);
for (PhoneNumber pn : returnedNumbers) {
System.out.println("Number: " + pn.getPhoneNumber());
}
return returnedNumbers;
}
// This runs in UI when background thread finishes
#Override
protected void onPostExecute(List<PhoneNumber> result) {
super.onPostExecute(result);
Intent viewContact = new Intent(context, WorksideContactCard.class);
ArrayList<PhoneNumber> arrlistPhoneNumbers =
new ArrayList<>(result);
viewContact.putParcelableArrayListExtra(
WORKSIDE_CONTACT_PHONE_NO, arrlistPhoneNumbers);
context.startActivity(viewContact);
System.out.println("Entered onPostExecute of fetchPhoneNumbers");
}
}.execute();
}
public void getPhoneNumbers(int id) {
return fetchPhoneNumbers(id);
}
}
store the contacts in a list in the doInBackground() method and start an intent to the new activity in the onPostExecute() method and with this intent pass the list of contacts as intent.extra() variables, retrieve and use them in the called activity.
Change your repository class to something like this
public class PhoneNumberRepository {
private WorksideDatabase worksideDatabase;
private List<PhoneNumber> returnedNumbers;
private Context mContext;
private boolean dataDownloaded;
public PhoneNumberRepository(Context context) {
String DB_NAME = "workside_database";
worksideDatabase = Room.databaseBuilder(context, WorksideDatabase.class, DB_NAME).build();
mContext = context;
}
public List<PhoneNumber> fetchPhoneNumbers(final int id) {
new AsyncTask<Integer, Void, List<PhoneNumber>>() {
#Override
protected List<PhoneNumber> doInBackground(Integer... ids) {
returnedNumbers = worksideDatabase.phoneNumberDao().getPhoneNumbersById(id);
System.out.println(returnedNumbers);
for (PhoneNumber pn : returnedNumbers) {
System.out.println("Number: " + pn.getPhoneNumber());
}
return returnedNumbers;
}
// This runs in UI when background thread finishes
#Override
protected void onPreExecute(List<PhoneNumber> result) {
//set flag to false when download starts
dataDownloaded = false;
}
// This runs in UI when background thread finishes
#Override
protected void onPostExecute(Object obj) {
super.onPostExecute(result);
//set flag to true once download completes, you can also check if response is null and update it accordingly
dataDownloaded = true;
System.out.println("Entered onPostExecute of fetchPhoneNumbers");
// for (PhoneNumber pn : result) {
// Toast.makeText(mContext, pn + "", Toast.LENGTH_SHORT).show();
// }
}
}.execute();
return returnedNumbers;
}
public List<PhoneNumber> getPhoneNumbers(int id) {
return fetchPhoneNumbers(id);
}
public boolean isDataDownloaded(int id) {
return dataDownloaded;
}
}
Use this function in onItemClick() whether your data is downloaded or not
if(phoneNumberRepository.isDataDownloaded()) {
//code to fetch data from phonenumberrepo and start activity
}

To whom does the value of return of loadInBackground () pass to?

(sorry, I don't know if my English is right, I hope it is right!)
loadInBackground() method,(in BookLoader class)returns a string value, but to who?
I looked for who calls loadInBackground() too but nobody does it.I read the official documentations too but without solutions.Thanks in advice friends.
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<String> {
public EditText mEditText;
public TextView mTextTitle, mTextAuthor;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEditText = (EditText) findViewById(R.id.bookInput);
mTextTitle = (TextView) findViewById(R.id.titleText);
mTextAuthor = (TextView) findViewById(R.id.authorText);
// to reconnect to the Loader if it already exists
if(getSupportLoaderManager().getLoader(0)!=null){
getSupportLoaderManager().initLoader(0,null,this);
}
}
public void searchBooks(View view) {
String queryString = mEditText.getText().toString();
// nasconde la tastiera
InputMethodManager inputManager = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
// Check the status of the network connection.
ConnectivityManager connMgr = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected() && queryString.length()!=0) {
mTextAuthor.setText("");
mTextTitle.setText(R.string.loading);
Bundle queryBundle = new Bundle();
queryBundle.putString("queryString", queryString);
getSupportLoaderManager().restartLoader(0, queryBundle,this);
}
else {
if (queryString.length() == 0) {
mTextAuthor.setText("");
mTextTitle.setText("Please enter a search term");
} else {
mTextAuthor.setText("");
mTextTitle.setText("Please check your network connection and try again.");
}
}
}
// Called when you instantiate your Loader.
#NonNull
#Override
public Loader<String> onCreateLoader(int i, #Nullable Bundle bundle) {
return new BookLoader(this, bundle.getString("queryString"));
}
#Override
public void onLoadFinished(#NonNull Loader<String> loader, String s) {
try {
JSONObject jsonObject = new JSONObject(s);
JSONArray itemsArray = jsonObject.getJSONArray("items");
int i = 0;
String title = null;
String authors = null;
while (i < itemsArray.length() || (authors == null && title == null)) {
// Get the current item information.
JSONObject book = itemsArray.getJSONObject(i);
JSONObject volumeInfo = book.getJSONObject("volumeInfo");
// Try to get the author and title from the current item,
// catch if either field is empty and move on.
try {
title = volumeInfo.getString("title");
authors = volumeInfo.getString("authors");
Log.d("TITLE", volumeInfo.getString("title"));
} catch (Exception e){
e.printStackTrace();
}
// Move to the next item.
i++;
}
// If both are found, display the result.
if (title != null && authors != null){
mTextTitle.setText(title);
mTextAuthor.setText(authors);
mEditText.setText("");
} else {
// If none are found, update the UI to show failed results.
mTextTitle.setText("no results");
mTextAuthor.setText("");
}
} catch (Exception e){
// If onPostExecute does not receive a proper JSON string, update the UI to show failed results.
mTextTitle.setText("no results");
mTextAuthor.setText("");
e.printStackTrace();
}
}
// Cleans up any remaining resources.
#Override
public void onLoaderReset(#NonNull Loader<String> loader) {
}
}
public class BookLoader extends AsyncTaskLoader<String> {
String mQueryString;
public BookLoader(#NonNull Context context, String queryString) {
super(context);
mQueryString = queryString;
}
#Nullable
#Override
public String loadInBackground() {
return NetworkUtils.getBookInfo(mQueryString);
}
#Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad();
}
}
I edited the post.
As the official documentation said:
onPostExecute(Result), invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.
Your return value is send to onPostExecute().
I encourage you to have a deeper look at this documentation

Update ListView asynchronously using Realm

My applications hang for a bit when I populate data from realm database to my listview.
So I planned to do it using Asynchronously so meanwhile data is collected I display a Loading dialogue here is the Code.
Already referred to this question by not able to implement in my case.
private class YourAsyncTask extends AsyncTask<String, String, RealmResults> {
ProgressDialog progressDialog;
#Override
protected void onPreExecute() {
// start loading animation maybe?
progressDialog = ProgressDialog.show(DictionarySscWords.this,
"ProgressDialog",
"Loading all words!");
}
#Override
protected RealmResults doInBackground(String... params) {
RealmConfiguration realmConfig = new RealmConfiguration.Builder(context).build();
Realm.setDefaultConfiguration(realmConfig);
realm = realm.getDefaultInstance();
RealmQuery<Word> query = realm.where(Word.class);
for (int i = 0; i < words_for_ssc[Integer.parseInt(params[0])].length; i++) {
if (i == words_for_ssc[Integer.parseInt(params[0])].length - 1) {
query = query.equalTo("word", words_for_ssc[Integer.parseInt(params[0])][i]);
} else {
query = query.equalTo("word", words_for_ssc[Integer.parseInt(params[0])][i])
.or();
}
}
sscresult = query.findAll(); //error 1
return sscresult;
}
#Override
protected void onPostExecute(RealmResults r) {
progressDialog.dismiss();
list.setAdapter(new MyAdapter(sscresult)); //error 2
realm.close();
}
}
ok so there are two problems if anyone can be solved my application would be error-free
if I try to run list.setAdapter(new MyAdapter(sscresult)); in background process the error is:-
this can run only in UI thread
if try to run in postExecute error is :-
Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
I am not able to solve this issue please help
You can have your query evaluated on a background thread using asynchronous query API in Realm.
private OrderedRealmCollectionChangeListener<RealmResults<User> callback = new OrderedRealmCollectionChangeListener<>() {
#Override
public void onChange(RealmResults<User> results, OrderedCollectionChangeSet changeSet) {
if (changeSet == null) {
// The first time async returns with an null changeSet.
} else {
// Called on every future update.
}
}
};
private RealmResults<User> result;
public void onStart() {
result = realm.where(User.class).findAllAsync();
result.addChangeListener(callback);
}
But if you give the RealmResults to a RealmRecyclerViewAdapter, then this is all automatic.
P.S. not closing Realm instance in doInBackground() is like, S-class horrible mistake. Please close your Realm instance on non-looping background threads.
Specifically the following:
// private class YourAsyncTask extends AsyncTask<String, String, RealmResults> {
//
// ProgressDialog progressDialog;
// #Override
// protected void onPreExecute() {
// // start loading animation maybe?
// progressDialog = ProgressDialog.show(DictionarySscWords.this,
// "ProgressDialog",
// "Loading all words!");
// }
//
// #Override
// protected RealmResults doInBackground(String... params) {
// RealmConfiguration realmConfig = new RealmConfiguration.Builder(context).build();
// Realm.setDefaultConfiguration(realmConfig);
// realm = realm.getDefaultInstance();
// RealmQuery<Word> query = realm.where(Word.class);
//
// for (int i = 0; i < words_for_ssc[Integer.parseInt(params[0])].length; i++) {
// if (i == words_for_ssc[Integer.parseInt(params[0])].length - 1) {
//
// query = query.equalTo("word", words_for_ssc[Integer.parseInt(params[0])][i]);
// } else {
// query = query.equalTo("word", words_for_ssc[Integer.parseInt(params[0])][i])
// .or();
//
// }
//
// }
// sscresult = query.findAll(); //error 1
// return sscresult;
//
// }
//
// #Override
// protected void onPostExecute(RealmResults r) {
// progressDialog.dismiss();
// list.setAdapter(new MyAdapter(sscresult)); //error 2
// realm.close();
// }
//}
and
public class MyActivity extends AppCompatActivity {
private RealmResults<Word> words;
private Realm realm;
private WordAdapter wordAdapter;
#BindView(R.id.recycler_view)
RecyclerView recyclerView;
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.my_activity);
ButterKnife.bind(this);
realm = Realm.getDefaultInstance();
words_for_ssc = ...
RealmQuery<Word> query = realm.where(Word.class);
String[] array = words_for_ssc[Integer.parseInt(params[0])];
for (int i = 0; i < array.length; i++) {
query = query.equalTo("word", array[i]);
if (i != array.length - 1) {
query = query.or();
}
}
words = query.findAllSortedAsync("word");
wordAdapter = new WordAdapter(words);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(wordAdapter);
}
#Override
public void onDestroy() {
super.onDestroy();
realm.close();
realm = null;
}
}
public class WordAdapter extends RealmRecyclerViewAdapter<Word, WordViewHolder> {
public class WordAdapter(OrderedRealmCollection<Word> words) {
super(words, true);
}
#Override
public WordViewHolder onCreateViewHolder(...) {
...
}
#Override
public void onBindViewHolder(WordViewHolder holder, int position) {
holder.bind(getData().get(position));
}
public static class WordViewHolder extends RecyclerView.ViewHolder {
public WordViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
}
public void bind(Word word) {
...
}
}
}
I think a cleaner solution to your problem without changing much of the code can be written below. In this case, everything that realm does happen on the background thread inside doInBackground. The realm instance is also closed on the thread it was created.
Now what I did basically is that I extracted a deep copy of the list of Words from RealmResult from realm.copyFromRealm(sscresult) which is completely detached from realm and can be moved around and modified inside any thread. All these objects are now free from realm and can be used in onPostExecute without any worries. The only thing you need to modify is the MyAdapter constructor which doesn't take a RealmResult but instead a List of Words which is exactly what you need and can be iterated the same way as RealmResult was.
The only downside of this approach is that the list of Words will not get synced automatically since they're detached and their value won't change automatically if they get altered inside Realm from somewhere else. But I'm pretty sure though that it won't bother you.
I'm also going to attach an official reference for realm.copyFromRealm() which is here.
private class YourAsyncTask extends AsyncTask<String, String, List<Word>> {
ProgressDialog progressDialog;
#Override
protected void onPreExecute() {
// start loading animation maybe?
progressDialog = ProgressDialog.show(DictionarySscWords.this,
"ProgressDialog",
"Loading all words!");
}
#Override
protected List<Word> doInBackground(String... params) {
RealmConfiguration realmConfig = new RealmConfiguration.Builder(context).build();
Realm.setDefaultConfiguration(realmConfig);
try(realm = realm.getDefaultInstance()) {
RealmQuery<Word> query = realm.where(Word.class);
for (int i = 0; i < words_for_ssc[Integer.parseInt(params[0])].length; i++) {
if (i == words_for_ssc[Integer.parseInt(params[0])].length - 1) {
query = query.equalTo("word", words_for_ssc[Integer.parseInt(params[0])][i]);
} else {
query = query.equalTo("word", words_for_ssc[Integer.parseInt(params[0])][i])
.or();
}
}
// Here's the sort. Use findAllSorted instead.
// You can change Sort.ASCENDING to Sort.DESCENDING to reverse
// the order.
sscresult = query.findAllSorted("word", Sort.ASCENDING);
// This is where the magic happens. realm.copyFromRealm() takes
// a RealmResult and essentially returns a deep copy of the
// list that it contains. The elements of this list is however
// completely detached from realm and is not monitored by realm
// for changes. Thus this list of values is free to move around
// inside any thread.
ArrayList<Word> safeWords = realm.copyFromRealm(sscresult);
realm.close();
return safeWords;
}
}
#Override
protected void onPostExecute(List<Word> words) {
progressDialog.dismiss();
// Please note here MyAdaptor constructor will now take the
// list of words directly and not RealmResults so you slightly
// modify the MyAdapter constructor.
list.setAdapter(new MyAdapter(words));
}
}
Hope it helps!

Android System.Err for setVisibility(View.GONE)?

I've noticed a bug in a basic survey app I'm making to better learn android.
Occasionally I get a W/System.errīš• at MainActivity.surveyAvailable(MainActivity.java:40) that points to this line of code:
button.setVisibility(View.GONE);
I've used setVisibility many times before and never had any issues.
Here's the function, this gets called when the user first enters the app, and after they finish taking a survey to check the server and see if there is another survey available for the user:
public void surveyAvailable(boolean surveyIsAvailable) {
Log.d("MainActivity", "App survey is available? " + surveyIsAvailable );
Button button = (Button)findViewById(R.id.takeSurveyButton);
if (surveyIsAvailable) {
button.setVisibility(View.VISIBLE);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
App.getInstance().showSurvey();
}
});
} else {
Log.d("MainActivity", "We hit here");
button.setVisibility(View.GONE);
}
}
When a survey isn't available, the appropriate lines are logged - App survey is available? false and 'We hit here'. But then the button sometimes doesn't get set to View.GONE and I see the System.Err line. But sometimes it works fine and the button's visibility does change. Any idea how to fix that? Or how to get more information on what the System.Err actually means?
EDIT:
I found that by setting Button surveyButton; in my activity and then referencing the button as this.surveyButton seems to get the functionality to work more along the lines of what we'd expect (e.g. when we call button.setVisibility(View.GONE) the view is actually consistently GONE). But it still throws the System.Err line which has me hesitant that things are working correctly.
Edited Activity:
public class MainActivity extends ActionBarActivity implements SurveyListener {
Button surveyButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.surveyButton = (Button)findViewById(R.id.takeSurveyButton);
}
public void surveyAvailable(boolean surveyIsAvailable) {
Log.d("MainActivity", "App survey is available? " + surveyIsAvailable );
if (surveyIsAvailable) {
this.surveyButton.setVisibility(View.VISIBLE);
this.surveyButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
App.getInstance().showSurvey();
}
});
} else {
Log.d("MainActivity", "We hit here");
this.surveyButton.setVisibility(View.GONE);
}
}
}
The activity implements this class:
public abstract interface SurveyListener
{
public abstract void surveyAvailable(boolean surveyAvailable);
}
Main App class that checks for surveys and calls 'surveyAvailable()`:
public class App
{
private static App _instance;
private SurveyListener _eventsHandler;
private String _apiKey = "";
private String _appuserId = "";
private String _surveyUrl = "";
private Activity _parentContext;
private Boolean _surveyAvailable;
public static App initWithApiKeyAndListener(String apiKey, SurveyListener surveyEventsHandler) {
if (_instance == null)
{
_instance = new App();
_instance._parentContext = (Activity) surveyEventsHandler;
_instance.setSurveyListener(surveyEventsHandler);
_instance.setApiKey(apiKey);
String appuserId = PreferenceManager.getDefaultSharedPreferences((Activity) _instance._eventsHandler).getString(tag, "no_appuser");
if (appuserId == "no_appuser") {
_instance._surveyAvailable = true;
_instance.alertAvailability(true);
} else {
_instance.checkForCampaigns();
}
}
return _instance;
}
private void alertAvailability(boolean surveyAvailable) {
App.getInstance()._eventsHandler.surveyAvailable(surveyAvailable);
}
private void checkForCampaigns() {
new CampaignCheck().execute();
}
public static App getInstance()
{
if (_instance == null)
{
_instance = new App();
}
return _instance;
}
public void donePushed()
{
App.getInstance().checkForCampaigns();
}
private class CampaignCheck extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... params) {
Boolean surveysAvailable = false;
try {
surveysAvailable = new AppuserConnection().checkCampaigns();
App.getInstance()._surveyAvailable = surveysAvailable;
App.getInstance().alertAvailability(_surveyAvailable);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
}
}
}
You shouldn't modify the UI elements from a different thread. You are doing this by calling App.getInstance().alertAvailability(_surveyAvailable); on a background thread. Move this to the AsyncTask's onPostExecute.

start AsyncTask from one activity, get result in another

I'm new to Android programming, and I'd like to create a central database service class which will take care of user data exchange with an external database. For this, I created a service which is started after successful login. I created another class that extends AsyncTask to do the data retrieval.
Now, I wanted the methods for the data retrieval to be stored in the service. I would fire intents to the service from different activities, and with .setAction() I would determine which method to call, or which data to retrieve.
I also created an interface class for handling the AsyncTask results.
Now, from this question I thought that it would be possible to have multiple listeners to one and the same AsyncTask result. But now this seems impossible to achieve: I'd like to retrieve the AsyncTask results in the MainMenuActivity, but I can't create an instance of AsyncUserData there as a delegate for the UserData class. In my example below, the missing piece is a valid instance of AsyncUserData for the UserData class to work with. How could I do it?
Here's the example:
MainMenuActivity
public class MainMenuActivity extends ActionBarActivity implements AsyncUserData {
TextView tvUsername;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_menu);
tvUsername =
(TextView) findViewById(R.id.tvUsername);
TelephonyManager tManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
String uid = tManager.getDeviceId();
getDataFromUserSessionService(this, uid);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void retrieveResult(String result) throws JSONException {
JSONObject jsonObject = new JSONObject(result);
String joName;
joName = jsonObject.getJSONObject("name").toString();
user.setName(joName);
tvUsername.setText(joName);
}
public void getDataFromUserSessionService(Context context, String uid) {
Intent intent = new Intent(context, UserSession.class);
intent.setAction(UserSession.ACTION_FETCH_USER_DATA);
intent.putExtra(UserSession.UID, uid);
context.startService(intent);
}
UserSession Service
public class UserSession extends IntentService {
public static final String ACTION_FETCH_USER_DATA = "com.example.blahblah.services.action.read_user_data";
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
utils = new Utils(this);
final String action = intent.getAction();
uid = intent.getStringExtra(UID);
if (ACTION_FETCH_USER_DATA.equals(action)) {
handleUserDataFetch(uid);
}
}
}
private void handleUserDataFetch(String uid) {
String[] parameters = new String[2];
parameters[0] = uid;
parameters[1] = Constants.USER_DATA_FETCH;
UserData userData = new UserData(this);
userData.execute(parameters);
}
UserData AsyncTask Class (the Utils class just has another post method):
public class UserData extends AsyncTask < String, Void, String > {
public AsyncUserData delegate = null;
private Context myContext;
public UserData(Context context) {
myContext = context;
}
#Override
protected String doInBackground(String...params) {
String serverResponse = "";
String uid = params[0];
Utils utils = new Utils(myContext);
String phpName = params[1];
List < NameValuePair > nameValuePairs = new ArrayList < NameValuePair > ();
nameValuePairs.add(new BasicNameValuePair("uid", uid));
try {
serverResponse = utils.passDataToServer(phpName, nameValuePairs);
} catch (IOException e) {
e.printStackTrace();
}
return serverResponse;
}
protected void onPostExecute(String result) {
try {
delegate.retrieveResult(result);
} catch (JSONException e) {
e.printStackTrace();
}
}
};
And the AsyncUserData interface:
public interface AsyncUserData {
void retrieveResult(String result) throws JSONException;
}
You can use a Singleton that stores a reference to the activity
public class ServiceToActivity
{
public ActionBarActivity mainactivity = null;
private static ServiceToActivity singleton = null;
public Class<?> cl = null;
private ServiceToActivity()
{
}
public static ActionBarActivity getSingleton()
{
if(singleton==null)
return null;
return singleton.mainactivity;
}
public static Class<?> getSingletonClass()
{
if(singleton==null)
return null;
return singleton.cl;
}
public static void setSingleton(ActionBarActivity mainactivity, Class<?> cl)
{
if(singleton==null)
singleton = new ServiceToActivity();
singleton.mainactivity = mainactivity;
singleton.cl = cl;
}
}
Then create the singleton before the service is started
public void getDataFromUserSessionService(Context context, String uid) {
Intent intent = new Intent(context, UserSession.class);
intent.setAction(UserSession.ACTION_FETCH_USER_DATA);
intent.putExtra(UserSession.UID, uid);
ServiceToActivity.setSingleton(this,this.getClass()); //create Singleton to store a reference to the activity
context.startService(intent);
}
In UserData retrieve data to the main activity by:
protected void onPostExecute(String result) {
try {
Class<?> cl = ServiceToActivity.getSingletonClass();
Method met = cl.getMethod("retrieveResult", String); //String because result is of type String: you can use result.getClass() instead
met.invoke(cl.cast(ServiceToActivity.getSingleton()), result); // compare it to this ServiceToActivity.getSingleton().retrieveResult(result);
} catch (JSONException e) {
e.printStackTrace();
}
}
It sounds like you might want to use an event bus such as otto

Categories