How to make the application wait until a thread has executed? - java

In my application, I have to send a list of accounts from one fragment to another, when the Activity starts. I get the list of accounts in the following thread, saving it in a global ArrayList. If the request to the server is good, the ArrayList gets populated with the necessary information. Then, i call the loadAccounts method before transferring data via bundle from one fragment to another. The problem is that the thread doesn't get to finish it's execution before I want to send the data between fragments, hence the ArrayList will be NULL when the data is sent.
How can I make the application wait until the thread executes, and only after that to send the data to the other fragment ?
My thread looks like this:
public void loadAccounts() {
LoadAccounts loadAccountsThread = new LoadAccounts(new Handler() {
public void handleResult(Result result) {
switch (result) {
case SUCCESSFUL_CODE:
accountsList = (ArrayList<Account>) accounts;
break;
case FAILED_CODE:
errorMsg = error.toString();
showDialog(errorMsg);
default:
break;
}
}
});
loadAccountsThread.start();
}
while in the onCreate method I do this:
loadAccounts();
Bundle args = new Bundle();
AccountsFragment fragment = new AccountsFragment ();
args.putSerializable("accounts", accountsList.get(0));
fragment.setArguments(args);
getSupportFragmentManager().beginTransaction()
.replace(R.id.rightConent, fragment).commit();
Any help would be appreciated. Thanks.

You do not want to make your application wait because it will make your app slow or even stuck in case of an error.
Do not send your accounts through the bundle. Instead create a method inside your AccountsFragment
public void setAccounts(ArrayList<Account> accounts){
//do whatever you need with your accounts here
}
and then inside your handleResult method when you have a SUCCESSFUL_CODE, run
fragment.setAccounts((ArrayList<Account>) accounts);
of course to do this, make sure your AccountFragment fragment is a field and not a local variable inside your onCreate. Make sure also that you instantiate your Fragment before running your thread

If you're calling web service use AsyncTaskinstead of thread and put this inside a method
Bundle args = new Bundle();
AccountsFragment fragment = new AccountsFragment ();
args.putSerializable("accounts", accountsList.get(0));
fragment.setArguments(args);
getSupportFragmentManager().beginTransaction()
.replace(R.id.rightConent, fragment).commit();
and call the method from onPostExecute() method using context

Related

How to remove OnCompleteListener on .get() method when fragment is destroyed?

My project uses Firestore as the database. I am having a fragment which when opened requests a document from Firestore in its onCreate method. I have attached an OnCompleteListener to the .get() method so that when the document fetch is complete, it updates UI.
The issue is that sometimes the user, after opening the fragment, quickly moves on to another fragment before the onCompleteListener is triggered. Or in some cases, the same fragment is called twice and while the first instance of the fragment is destroyed, its onCompleteListener is still alive and triggers after the first instance of the Fragment is destroyed. In both these scenarios, I get an exception
Fatal Exception: java.lang.IllegalStateException: Fragment ProfileFragment{4ee762 (a8f7ae01-23be-4d47-b695-68e273b992bf)} not attached to a context.
at androidx.fragment.app.Fragment.requireContext(Fragment.java:774)
at androidx.fragment.app.Fragment.getResources(Fragment.java:838)
at androidx.fragment.app.Fragment.getString(Fragment.java:860)
at com.desivideshi.productinfo.ProfileFragment$18.onComplete(ProfileFragment.java:903)
at com.google.android.gms.tasks.zzj.run(com.google.android.gms:play-services-tasks##17.1.0:4)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at com.google.android.gms.internal.tasks.zzb.dispatchMessage(com.google.android.gms:play-services-tasks##17.1.0:6)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6898)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Here is my code snippet inside ProfileFragment's onCreate method
documentReference.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (mContext != null){
if (task.isSuccessful() && task.getResult() != null && task.getResult().getData() != null) {
Map<String, Object> productsDoc = task.getResult().getData();
productsList = (List<Map<String, String>>) productsDoc.get(getString(R.string.fs_f_products_list));
if(productsList == null){
productsList = new ArrayList<>();
}
mMyProfileData.setMyProductsList(productsList);
}
} else {
Toast.makeText(mContext, "Failed to load products", Toast.LENGTH_SHORT).show();
}
createTabLayout();
profileTabLayout.selectTab(profileTabLayout.getTabAt(profileTabLayout.getSelectedTabPosition()));
progressLayoutProfileTab.setVisibility(View.GONE);
}
}
});
Is there a way to remove the callback to OnCompleteListener which I can place in the onDestry() or onDetach() method of the Fragment in order to avoid this exception?
How to remove OnCompleteListener on .get() method when fragment is destroyed?
There is no need to remove any listeners, because the get() method gets the data exactly once and that's the end of it. If you have been used addSnapshotListener(), you should have removed the listener according to the life-cycle of your activity. In your case, the onComplete() method will fire only when the data is completely loaded from the database. What you need to know is that the Cloud Firestore client runs all network operations in a background thread. So don't do things that related to a context in a background thread.
If you need a real-time listener, then use the addSnapshotListener() and stop listening for changes once your activity/fragment is stoped. Please also remember that onDestroy is not always called, so stop listening for changes in onStop().
Edit:
Because you are using things that are related to a context inside the callback, you should query the database only is isAdded() method returns true.
If you are going to make changes to views directly in your listener (which is not really a good idea, more on that later), then you use should an activity-scoped listener on the Task object returned by get(), so that the listener won't trigger after the activity becomes stopped.
documentReference.get().addOnCompleteListener(activity, callback)
Note that you're passing the activity instance as the first argument, before the callback.
However, it would be better for modern Android applications to use MVVM architecture, as recommended by Google throughout Android documentation, to abstract away the database code from the views by exposing only a LiveData, which is aware of the activity lifecycle and will not emit events to observers after the activity stops. But that's entirely up to you.
The most easy way I think could be use a try catch so when you get the exception, nothing happens, it's a controlated exception.
The best way of solve this problems is to use a MVVM pattern with LiveData. ViewModel is aware of the lifeCycle of the activity / fragment and when your fragment dies, you don't get any background notifications from calls you did when the fragment was alive.
Try this
documentReference.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
try{
if (mContext != null){
if (task.isSuccessful() && task.getResult() != null && task.getResult().getData() != null) {
Map<String, Object> productsDoc = task.getResult().getData();
productsList = (List<Map<String, String>>) productsDoc.get(getString(R.string.fs_f_products_list));
if(productsList == null){
productsList = new ArrayList<>();
}
mMyProfileData.setMyProductsList(productsList);
}
} else {
Toast.makeText(mContext, "Failed to load products", Toast.LENGTH_SHORT).show();
}
createTabLayout();
profileTabLayout.selectTab(profileTabLayout.getTabAt(profileTabLayout.getSelectedTabPosition()));
progressLayoutProfileTab.setVisibility(View.GONE);
}
}catch(Exception e){
//Use the exception inside a log, let it blank or as you prefer
}
}
});

Strange LiveData behaviour?

Im trying to implement MVVM architecture using ViewModel and LiveData. These two methods are inside a Activity:
private void handleResult(BoardViewModel vm) {
vm.getLiveDataSingleObj("Result").observe(this, new Observer<Object>() {
#Override
public void onChanged(#Nullable Object resultObj) {
Result result = (Result) resultObj;
if (!result.isCompleted()) return;
gotoResult();
}
});
}
And
private void gotoResult() {
Log.w(LOG_TAG, "Result: Moving to next activity");
Intent intent = new Intent(boardActivity, ResultActivity.class);
intent.putExtra("LEVEL", levelIndex);
intent.putExtra("MAP", mapIndex);
startActivity(intent);
}
The handleResult method is setup to listen for result objects that indicate that the game has ended and it is time to move on to the next activity ("gotoResult"). However, this completely breaks the navigation of the app, when i go back and then say attempt to start a new game session i instead instantly go to the next activity telling me I've already won.
Any ideas as to why it fires multiple times and eventually stops, letting me start a new session. To clarify, if I remove the gotoResult the logic works every single time no errors with indexes out of bounds or what have you, it's only when I add the goto that everything breaks.
ViewModel:
private void setupHashTypes() {
hashLiveData.put(KEY_BOARD, liveDataBoardQuery);
hashLiveData.put(KEY_STEPS_COUNTER, game.getStepsTakenLiveData());
hashLiveData.put(KEY_PATH_CHANGE, game.getPathChangedLiveData());
hashLiveData.put(KEY_VALUE_CHANGE, game.getValueChangeLiveData());
hashLiveData.put(KEY_TIMER, game.getTimerLiveData());
hashLiveData.put(KEY_SELECTED, game.getSelectedLiveData());
hashLiveData.put(KEY_DESELECTED, game.getDeselectedLiveData());
hashLiveData.put(KEY_HOLD, game.getHoldLiveData());
hashLiveData.put(KEY_UNHOLD, game.getUnholdLiveData());
hashLiveData.put(KEY_RESULT, game.getResultLiveData());
}
public LiveData<Object> getLiveDataSingleObj(String type) {
if (hashLiveData.containsKey(type)) {
return (LiveData<Object>) hashLiveData.get(type);
}
throw new IllegalArgumentException("Invalid: key was not found: " + type);
}
And the Model has getters, example:
private final SingleLiveEvent<Result> resultLiveData = new SingleLiveEvent<>();
public LiveData<Result> getResultLiveData() {
return resultLiveData;
}
you should remove the observer in onDestroy() method
Changing from MutableLiveData which always resends the previous set values to new subscribers, to SingleLiveEvent which doesn't have this behaviour, solved the problem.
The class can be found here: https://github.com/googlesamples/android-architecture/tree/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp

Android: Why won't this code run in onCreate, or how can I make it work without a thread?

I have this piece of code that runs alright when I put it in Eclipse, but for some reason it does not want to execute when I put it in an activity's onCreate method in Android studio.
Here is the code:
public class ItemListView extends AppCompatActivity{
String itemURL;
int listSize;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item_list_view);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
Bundle itemData = getIntent().getExtras();
if(itemData==null){
return;
}
//Gets URL from search bar
itemURL = itemData.getString("itemURL");
try {
Document doc = Jsoup.connect("https://www.amazon.com/s/ref=nb_sb_ss_c_1_6?url=search-alias%3Daps&field-keywords=rx+390&sprefix=rx+390%2Caps%2C166&crid=2MTUBA4KGNY06").get();
String link = doc.select("h2#s-result-count").first().text();
System.out.println(link);
System.out.println(link.substring(1));
if (link.substring(1, 2).equals("-")) {
System.out.println("run1");
listSize = Integer.parseInt(link.substring(2, 3));
System.out.println(listSize);
try {
listSize = Integer.parseInt(link.substring(2, 4));
System.out.println(listSize);
} catch (Exception e) {
}
} else {
System.out.println("run2");
listSize = Integer.parseInt(link.substring(0, 1));
System.out.println(listSize);
try {
listSize = Integer.parseInt(link.substring(0, 2));
System.out.println(listSize);
} catch (Exception e) {
}
}
} catch (Exception e) {
}
System.out.println("listSize: " +listSize);
...
}
}
I need listSize to create a variable array depending on the URL, but when I print the value to make sure it's working it always gives me 0. I have tried running the code in a separate Java Class with AsyncTask and it works, but by the time the code executes in onPostExecute, it's too late since the class above has already tried to initialize the array.
Any advice would be appreciated, thanks!
What you need is a callback to allow you to initialize variable onPostExecute:
interface OnCallCompleteCallBack {
void onCallComplete(int listSize);
}
In your AsyncTask do this:
public class MyTask extends AsyncTask < ... > {
// Maintain a ref to callback
OnCallCompleteCallBack callback;
MyTask(OnCallCompleteCallBack callback) {
this.callback = callback;
}
...
#Override
protected void onPostExecute(Void aVoid) {
if (callback != null) {
callback.onCallComplete(listSize);
}
}
}
Make your Activity implement OnCallCompleteCallBack and start the AsyncTask like this:
new MyTask(this).execute();
You can then use the value inside your activity's implementation of onCallComplete()
Before I answer, just a few observations:
Naming your activity ItemListView seems wrong (an Activity is not a View).
You should never perform networks calls on the UI thread.
If you want to log, you should use Log instead of System.out.println().
And now to the answer. You should execute the code that fetches the data in an AsyncTask (as you mentions it works). Once the data is fetched, you should update array and at that point update the UI in onPostExecute().
Android works pretty well using the MVC (Model-View–Controller) pattern and your problem is a classic case where you need to update the model using data from the server and update the views accordingly. The activity represents controller in this case.
Please go through the topic in android developer site Android Developer, Read the section "Introducing Network Operations on a Separate Thread" - To avoid creating an unresponsive UI, don't perform network operations on the UI thread. By default, Android 3.0 (API level 11) and higher requires you to perform network operations on a thread other than the main UI thread; if you don't, a NetworkOnMainThreadException is thrown.
Thanks Ashish

java.lang.IllegalStateException Fragment not attached to Activity

After I receive a response with Volley, I have to get back to the main fragment.
I have two different volley requests , depending on some condition, I'll call it 'a' in this example.
The weird thing is the when a==1, popBackStack gets me successfully to the main fragment .
When a==0 it crashes and I receive java.lang.IllegalStateException Fragment not attached to Activity
I tried creating a new main fragment (transaction.commit....) but it didn't help.
if( a == 0 )
{
VolleyManager.add(jsnObj,
new RequestListener() {
#Override
public <T> void onSuccess(T object) {
mFragmentManager.popBackStack(DataManager.BACK_STACK_KEY_MAIN_FRAGMENT, 0);
}
});
}
else if( a==1 )
{
VolleyManager.update(jsnObj,
new RequestListener() {
#Override
public <T> void onSuccess(T object) {
mFragmentManager.popBackStack(DataManager.BACK_STACK_KEY_MAIN_FRAGMENT, 0);
}
});
}
Error -
java.lang.IllegalStateException: Fragment MainFragment{6aaaf7f} not attached to Activity
at android.app.Fragment.getResources(Fragment.java
The problem seems to be with the getResources(), but I do the same thing when a==1 and I've got no problems at all.
It seems like that by the time AsyncTask finishes and calls onPostExecute, the MainFragment has been detached from its activity. So either the activity has already been destroyed or fragment was never attached.
So if fragment is not attached to the activity, it can't access resources because that requires context and fragment doesn't have but activity does.
So you should check if activity is null or not before calling getResources.
Update the code like this:
if(getActivity()!=null){
String streetFormat = getActivity().getResources().getString( R.string.address_name_string );
....
}
You have to cancel your requests on
onDestroyView()
method of the fragment or check if the fragment is already alive and added to host activity or not
I'd go with something like this:
onDestroyView(){ Volley.cancelAllRequests() }
or
onResponse(){ if(getActivity() != null && isAdded(){ // here handle the response and update views, otherwise just cache the response!}}
getResources() must be called from something that has Context, like the activity. The fragment itself does not have the Context since it does not implement it. If you're using getResources() in a fragment, you can try this:
String streetFormat = getActivity().getResources().getString( R.string.address_name_string );

Executing thread only in one Android Activity

I have three java files in my Android project. Two are activities (MainActivity and GeoActivity) and one is a plain java file (PostHttp -> sends data to server via the HTTP POST)
I switch over to GeoActivity via a simple button on-click method. GeoActivity returns the co-ordinates of the current location in a TextView AND sends them to a remote server via the HTTP POST.
I have a Handler.class which executes sends the Post Message after a delay of 50s. Something like this below. The problem i have is that when i click the back button and switch over to MainActivity i can still see in LogCat the echoes receiving from the server that the data is still being sent. How can i stop that?
GeoActivity.class
public class GeoActivity extends Activity {
Location location;
private Handler mHandler = new Handler();
protected void onCreate(Bundle savedInstanceState) {
....
if(location != null){
mHandler.postDelayed(updateTask,0);
}
...
}
...
public Runnable updateTask = new Runnable(){
public void run(){
mlocListener.onLocationChanged(location);
//send coordinates with a delay of 50s
new PostHttp(getUDID(),latitude,longitude).execute();
mHandler.postDelayed(updateTask, 50000);
}
Try acting on the activity's life cycle.
For example:
#Override
protected void onStop() {
super.onStop(); // Always call the superclass method first
// Save the note's current draft, because the activity is stopping
// and we want to be sure the current note progress isn't lost.
ContentValues values = new ContentValues();
values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());
getContentResolver().update(
mUri, // The URI for the note to update.
values, // The map of column names and new values to apply to them.
null, // No SELECT criteria are used.
null // No WHERE columns are used.
);
}
This doesn't destroy the activity, it will reside in memory. However, you can always resume when needed.
Source:
Stopping and Restarting Android Activities

Categories

Resources