Android dynamic fragment loading progress bar visibility won't set - android

I'm using Android fragments to load the code in my application. To create a simple loader I have LoadingFragment extends Fragment and then my fragment classes extend that, for example: MyFragment extends LoadingFragment.
The LoadingFragment has hideLoader and showLoader which theoretically my subclass Fragment should be able to call in onCreateView and onPostExecute to show and hide a progress bar in between loads.
Layout wise I have a main_layout.xml which has a framelayout for the dynamic fragments and a static relativelayout that houses the progress bar.
At the moment my fragments load and replace from within one another onclick of elements but I have removed that code.
The Problem
The issue is the setVisibilty in LoadingFragment seems to have zero effect when calling it from the subclasses, for example MyFragment, why is this?
I have given LoadingFragment it's own View variable of viewMaster which I believe should reference main_layout but still the visibility changes seem to have no effect?
MainActivity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyFragment myFragment = MyFragment.newInstance();
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_holder, myFragment).commit();
}
}
LoadingFragment
public class LoadingFragment extends Fragment {
View viewMaster;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
viewMaster = inflater.inflate(R.layout.main_layout, container, false);
return viewMaster;
}
public void showLoader() {
viewMaster.findViewById(R.id.fragment_holder).setVisibility(View.INVISIBLE);
viewMaster.findViewById(R.id.loading).setVisibility(View.VISIBLE);
}
public void hideLoader() {
viewMaster.findViewById(R.id.fragment_holder).setVisibility(View.VISIBLE);
viewMaster.findViewById(R.id.loading).setVisibility(View.INVISIBLE);
}
}
MyFragment
public class MyFragment extends LoadingFragment {
View view;
public MyFragment() {}
public static MyFragment newInstance() {
MyFragment fragment = new MyFragment();
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
super.showLoader();
view = inflater.inflate(R.layout.fragment_lans, container, false);
MyFragment.ApiCallJob apicalljob = new MyFragment.ApiCallJob();
apicalljob.execute("a string");
return view;
}
public class ApiCallJob extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String[] params) {
// do things
}
#Override
protected void onPostExecute(String data) {
// do things
// tried both to be sure but they should do the same?
hideLoader();
MyFragment.super.hideLoader();
}
}
}
main_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="app.stats.MainActivity"
android:orientation="horizontal">
<FrameLayout
android:id="#+id/fragment_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
<RelativeLayout
android:id="#+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/progress_bar"/>
</RelativeLayout>
</RelativeLayout>

The issue is the setVisibility in LoadingFragment seems to have zero effect when calling it from the subclasses, for example MyFragment, why is this?
Let's look at what happens when MyFragment onCreateView is called:
You call super onCreateView which is in LoadingFragment. This inflates main_layout and returns the inflated view. Note that this view is not referenced in MyFragment (i.e. View mainLayout = super.onCreateView(inflater, container, savedInstanceState);)
You just inflated the view that has loading view group, then threw it away. However, you did manage to save a reference to that view in the LoadingFragment superclass.
Next you inflate R.layout.fragment_lans and (after starting your load) you return that view as the view to be used in the fragment.
So the thing to observe here is that LoadingFragment now has a reference to a view that is nowhere in the fragment's active view hierarchy.
Given that, it's no wonder that setVisibility doesn't do anything, because the fragment isn't displaying that view.
I wouldn't use inheritance to do this, but if you must use it, here's how to fix your problem.
Let's just use the ProgressBar without the view group wrapper. Since it's in a RelativeLayout, let's center it. Then make it android:visibility="gone" so it's effectively out of the fragment layout:
<ProgressBar
android:id="#+id/progress_bar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="gone"/>
You're going to put this progress bar in the layout for every LoadingFragment subclass, including fragment_lans.xml. You can use the <include> tag to make life easier here.
Change the LoadingFragment so that a) it doesn't interfere with the inflation of its subclass' layouts and b) it uses the progress bar in the subclass' active view hierarchy:
public class LoadingFragment extends Fragment {
public void showLoader() {
if (getView() != null) {
View progressBar = getView().findViewById(R.id.progress_bar);
if (progressBar != null) {
progressBar.setVisibility(View.VISIBLE);
}
}
}
public void hideLoader() {
if (getView() != null) {
View progressBar = getView().findViewById(R.id.progress_bar);
if (progressBar != null) {
progressBar.setVisibility(View.GONE);
}
}
}
}
Now you should see what you expect.
EDIT
If you really want to use the layout scheme you started with, here is my recommendation:
Your activity is inflating main_layout that has the progress bar, so I would give some of the responsibility to the activity.
Now, you could just write some code for the fragment that would trawl around the view hierarchy and look for the progress bar. That wouldn't be my preference.
Here's another approach:
Create an interface
interface ProgressDisplay {
public void showProgress();
public void hideProgress();
}
Make the activity implement it
public class MainActivity extends AppCompatActivity implements ProgressDisplay {
...
public void showProgress() {
findViewById(R.id.loading).setVisibility(View.VISIBLE);
}
public void hideProgress() {
findViewById(R.id.loading).setVisibility(View.GONE);
}
Then add the methods to LoadingFragment that call the activity:
protected void showProgress() {
if (getActivity() instanceof ProgressDisplay) {
((ProgressDisplay) getActivity()).showProgress();
}
}
protected void hideProgress() {
if (getActivity() instanceof ProgressDisplay) {
((ProgressDisplay) getActivity()).hideProgress();
}
}
This way you can take any activity that inflates a layout with your progress view and have it implement ProgressDisplay so you can use the LoadingFragment class with it.
You could also make a LoadingActivity class and subclass that, too.

You calling setVisibility via showLoader on View which is not used to draw on screen becouse you returning other View as fragment view.
Something like that should work (MyFragment):
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = super.onCreateView(inflater, container, savedInstanceState);
ViewGroup vg = (ViewGroup) v.findViewById(R.id.fragment_holder)
inflater.inflate(R.layout.fragment_lans, vg, true);
MyFragment.ApiCallJob apicalljob = new MyFragment.ApiCallJob();
showLoader();
apicalljob.execute("a string");
return v;
}

If you are using inheritance then you cant assign layout to parent fragment.
You can take advantage of inheritance for View Binding with butterknife like this and write much clean code.
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import com.emerson.techdayapp.R;
import com.emerson.techdayapp.view.activity.MainActivity ;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
/**
* Base fragment created to be extended by every fragment in this application. This class provides
* dependency injection configuration, ButterKnife Android library configuration and some methods
* common to every fragment.
*/
public abstract class BaseFragment extends Fragment {
private Unbinder unbinder;
#BindView(R.id.htab_toolbar)
Toolbar mToolbar;
#BindView(R.id.loading_view)
ProgressBar loadingView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(getFragmentLayout(), container, false);
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//bind view
injectViews(view);
//set page title
setUpToolBar(getPageTitle());
//add click listeners
setUpViewEvents();
//make service call
fillData();
}
/**
* #return resource if of fragment layout
*/
protected abstract int getFragmentLayout();
/**
* Set toolbar title
*
* #return
*/
protected abstract String getPageTitle();
/**
* Add click listeners
*/
protected abstract void setUpViewEvents();
/**
* Make Web serve calls here
*/
protected abstract void fillData();
/**
* Inject View
*
* #param view
*/
private void injectViews(final View view) {
unbinder = ButterKnife.bind(this, view);
}
/**
* Set up toolbar
*
* #param pageTitle
*/
private void setUpToolBar(String pageTitle) {
getActivity().setTitle(pageTitle);
if (mToolbar != null) {
((MainActivity ) getActivity()).setSupportActionBar(mToolbar);
((MainActivity ) getActivity()).getSupportActionBar()
.setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationIcon(R.drawable.ic_drawer);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//TODO what to do on back press
}
});
mToolbar.setTitleTextColor(Color.WHITE);
}
}
public void showProgressDialog() {
if (null != loadingView)
loadingView.setVisibility(View.VISIBLE);
}
public void dismissProgressDialog() {
if (null != loadingView)
loadingView.setVisibility(View.GONE);
}
#Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}
And then use base fragment like this
public class MyFragment extends BaseFragment {
public MyFragment() {
}
public static MyFragment newInstance() {
MyFragment fragment = new MyFragment();
return fragment;
}
#Override
protected int getFragmentLayout() {
//put your frag layout id here
return R.layout.frag_pulse;
}
#Override
protected String getPageTitle() {
//Add some title to page
return "Some Title";
}
#Override
protected void setUpViewEvents() {
}
#Override
protected void fillData() {
new AsyncTask<String, Void, String>() {
#Override
protected void onPreExecute() {
super.onPreExecute();
//Show Progress
showProgressDialog();
}
#Override
protected String doInBackground(String... strings) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
//Hide Progress
dismissProgressDialog();
}
};
}
}
Things to note here are your fragment layout must have one toolbar id htab_toolbar and a progress bar with id loading_view as this are used by base class

Related

How to hide android fragment in activity to only have one fragment active at the time?

So basically, i have an activity called (Profile Activity) and two fragments connected to it (Profile view and profile edit fragment). Since im completely new with android studio, java language and fragments, im trying to place both fragments into activity but in a way that only profile view fragment is shown. Edit profile fragment needs to be hidden. Im using next part of the code:
getSupportFragmentManager().beginTransaction().add(R.id.profile_fragment, profileViewFragment).commit();
getSupportFragmentManager().beginTransaction().add(R.id.profile_fragment, profileEditFragment).commit();
I already tried something with "hide" and "show", but with no success. I have imported "android.support.v4.app.FragmentManager;" Thank you.
EDIT:
Profile activity after implementing new code:
public class ProfileActivity extends AppCompatActivity implements ProfileViewFragment.ProfileViewListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
//ovo ispod je za proslijedivanje iz activita u fragment
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
ProfileViewFragment profileViewFragment = new ProfileViewFragment();
ProfileEditFragment profileEditFragment=new ProfileEditFragment();
profileViewFragment.setArguments(getIntent().getExtras());
profileEditFragment.setArguments(getIntent().getExtras());
//getSupportFragmentManager().beginTransaction().add(R.id.profile_fragment, profileEditFragment).commit();
getSupportFragmentManager().beginTransaction().add(R.id.profile_fragment, profileViewFragment).commit();
//getSupportFragmentManager().beginTransaction().replace(R.id.profile_fragment, profileViewFragment).commit();
//FragmentManager fm=getSupportFragmentManager();
//fm.beginTransaction().setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out).show(new ProfileViewFragment()).commit();
//fm.beginTransaction().setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out).show(new ProfileEditFragment()).commit();
//-----------------------------------
}
#Override
public void onOpenProfileEditor() {
ProfileEditFragment profileEditFragment=new ProfileEditFragment();
profileEditFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction().replace(R.id.profile_fragment, profileEditFragment).commit();
}
#Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof ProfileViewFragment) {
ProfileViewFragment profileFragment = (ProfileViewFragment) fragment;
profileFragment.setListener(this::onOpenProfileEditor);
}
}
}
Profile view fragment with new code:
public class ProfileViewFragment extends Fragment {
private Unbinder unbinder;
//novi kod sa stacka
private ProfileViewListener listener;
//-------------
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
FragmentProfileViewBinding viewBinding=DataBindingUtil.inflate(inflater, R.layout.fragment_profile_view, container, false);
View view=viewBinding.getRoot();
unbinder = ButterKnife.bind(this, view);
UserModel user = (UserModel) getArguments().get(ModelEnum.UserModel.name());
//viewBinding povezuje fragment i xml (proslijeduje user)
viewBinding.setUser(user);
//viewBinding.setUserGender(user);
return view;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
// #OnClick(R.id.btn_change_settings)
// public void changeSettings(){
//getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.profile_fragment, new ProfileEditFragment()).commit();
// }
#Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
//ISPOD JE NOVI KOD SA STACK OVERFLOWA
public interface ProfileViewListener{
void onProfileEditor();
}
public void setListener(ProfileViewListener listener) {
this.listener = listener;
}
#OnClick(R.id.btn_change_settings)
public void onEdit(View view){
if(listener!=null){
onOpenProfileEditor();
}
}
}
Instead of
getSupportFragmentManager().beginTransaction().add(R.id.profile_fragment, profileEditFragment).commit();
It must be
getSupportFragmentManager().beginTransaction().replace(R.id.profile_fragment, profileEditFragment).commit();
This will replace your fragment, instead of adding it.
Please, also note that you must call "add" for the first time and use "replace" afterwards.
You may find more about fragments here: https://developer.android.com/training/basics/fragments/fragment-ui
EDIT
For the new issue you have outlined, the solution is to "report" you activity that an event had happened, so it can take action. Here is how to do that.
First, we need an interface (you can add it inside you Profile fragment) and to link the activity to our fragment, if it implements that interface.
public class ProfileViewFragment extends Fragment {
...
...
private ProfileViewListener listener;
...
...
#OnClick(R.id.btn_change_settings)
public onEdit(View view) {
// If there is anyone listening, report that we need to open editor
if (listener != null) {
listener .onOpenProfileEditor();
}
}
public void setListener(ProfileViewListener listener) {
this.listener = listener;
}
// The interface
public interface ProfileViewListener {
void onOpenProfileEditor();
}
}
And in the class, we need to implement the interface and subscribe as a listener.
public class ProfileActivity extends AppCompatActivity implements ProfileViewFragment.ProfileViewListener {
...
...
#Override
public void onOpenProfileEditor() {
ProfileEditFragment profileEditFragment=new ProfileEditFragment();
profileEditFragment.setArguments(getIntent().getExtras());
getSupportFragmentManager()
.beginTransaction
.replace(R.id.profile_fragment, profileEditFragment)
.commit();
}
#Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof ProfileViewFragment) {
ProfileViewFragment profileFragment = (ProfileViewFragment) fragment;
profileFragment.setListener(this);
}
}
}
You may find more detail on Activity-Fragment communication here - https://developer.android.com/training/basics/fragments/communicating

Why does pressing back from detail activity after landscape-to-portrait-switch show an empty screen?

Below is the MainActivity class that I'm using. The code checks to see if the phone is in landscape or portrait. If it's in portrait, it will show the main fragment in the main activity only (the main fragment is a static fragment in the main_activity.xml file). Then if a "Recipe" is clicked it will open a detail activity with its own fragment. If the phone is in landscape mode, it will show the main fragment and the detail fragment side by side. Everything works perfectly fine however when I follow the procedure below I get a white screen instead of the main activity:
Procedure:
Switch to landscape
Switch back to portrait
Choose an item and wait for the detail activity to open
Press back
Here instead of the main activity window I get a white screen
If I don't switch to landscape and just start with the portrait mode everything is fine. It seems like switching to landscape does something that causes the problem and I can't figure out what. Any tip on what's going on or where to look would be much appreciated.
public class MainActivity extends AppCompatActivity implements RecipesFragment.OnRecipeClickListener {
private String RECIPE_PARCEL_KEY;
private boolean mTwoPane;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RECIPE_PARCEL_KEY = getString(R.string.ParcelKey_RecipeParcel);
if (findViewById(R.id.linearLayoutTwoPane) != null) {
mTwoPane = true;
if (savedInstanceState == null) {
RecipeFragment recipeFragment = new RecipeFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.add(R.id.recipeFrameForTwoPane, recipeFragment)
.commit();
}
} else {
mTwoPane = false;
}
}
#Override
public void OnRecipeClick(Recipe recipe) {
if (mTwoPane) {
RecipeFragment recipeFragment = new RecipeFragment();
recipeFragment.setRecipe(recipe);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.recipeFrameForTwoPane, recipeFragment)
.commit();
} else {
Class destinationClass = DetailActivity.class;
Intent intentToStartDetailActivity = new Intent(this, destinationClass);
intentToStartDetailActivity.putExtra(RECIPE_PARCEL_KEY, recipe);
startActivity(intentToStartDetailActivity);
}
}
}
EDIT:
Adding RecipeFragment's code below:
public class RecipeFragment extends Fragment {
private Recipe mRecipe;
#BindView(R.id.tv_recipeName) TextView recipeNameTextView;
public RecipeFragment(){
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.recipe_fragment,container,false);
ButterKnife.bind(this,view);
if(mRecipe!=null) {
recipeNameTextView.setText(mRecipe.getName());
}else{
recipeNameTextView.setText(getString(R.string.messageSelectARecipe));
}
return view;
}
public void setRecipe(Recipe recipe){
mRecipe = recipe;
}
}
EDIT:
I followed #mt0s's advice and created different background colors for the fragments and activities and finally narrowed down the problem to a line in my recyclerview adapter code. My adapter code is below. Inside loadInBackground() on line URL url = new URL(getString(R.string.URL_RecipeJSON)); I get a Fragment RecipesFragment{96e9b6a} not attached to Activity exception. I don't understand why I'm getting this exception and what the best way to resolve this is. Have I placed the right code in the right fragment methods (ie OnCreate vs OnActivityCreated vs OnCreateView vs etc)?
public class RecipesFragment extends Fragment
implements RecipeAdapter.RecipeAdapterOnClickHandler,
LoaderManager.LoaderCallbacks<ArrayList<Recipe>> {
#BindView(R.id.rv_recipes) RecyclerView mRecyclerView;
private RecipeAdapter mRecipeAdapter;
private static final int LOADER_ID = 1000;
private static final String TAG = "RecipesFragment";
private OnRecipeClickListener mOnRecipeClickListener;
public RecipesFragment(){
}
public interface OnRecipeClickListener {
void OnRecipeClick(Recipe recipe);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.recipes_fragment, container, false);
ButterKnife.bind(this, view);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setHasFixedSize(true);
mRecipeAdapter = new RecipeAdapter(this);
mRecyclerView.setAdapter(mRecipeAdapter);
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
#Override
public void onPause() {
super.onPause();
}
#Override
public void onResume() {
super.onResume();
}
#Override
public void OnClick(Recipe recipe) {
mOnRecipeClickListener.OnRecipeClick(recipe);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try{
mOnRecipeClickListener = (OnRecipeClickListener) context;
} catch (ClassCastException e){
Log.e(TAG, "onAttach: Host activity class must implement OnRecipeClickListener.");
}
}
#Override
public Loader<ArrayList<Recipe>> onCreateLoader(int i, Bundle bundle) {
return new AsyncTaskLoader<ArrayList<Recipe>>(getActivity()) {
#Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad();
}
#Override
public ArrayList<Recipe> loadInBackground() {
String response;
ArrayList<Recipe> recipes = null;
try {
URL url = new URL(getString(R.string.URL_RecipeJSON)); //***I get an exception here***
response = NetworkUtils.getResponseFromHttpUrl(url, getActivity());
recipes = RecipeJsonUtils.getRecipeFromJson(getActivity(), response);
} catch (Exception e) {
Log.e(TAG, "loadInBackground: " + e.getMessage());
}
return recipes;
}
};
}
#Override
public void onLoadFinished(Loader<ArrayList<Recipe>> loader, ArrayList<Recipe> recipes) {
mRecipeAdapter.setRecipeData(recipes);
}
#Override
public void onLoaderReset(Loader<ArrayList<Recipe>> loader) {
}
}
I finally figured out the problem and the solution. The problem is that onStartLoading() in the AsyncTaskLoader anonymous class in RecipesFragment class gets called every time the fragment is resumed whether the enclosing Loader is called or not. This causes the problem. I need to have control over when onStartLoading() is being called and I only want it to be called if and only if the enclosing Loader is being initialized or restarted. As such, I destroyed the loader in onPause() of the fragment and restarted it in onResume(). Hence, I added the following code to the RecipesFragment class:
#Override
public void onPause() {
super.onPause();
getLoaderManager().destroyLoader(LOADER_ID);
}
#Override
public void onResume() {
super.onResume();
getLoaderManager().restartLoader(LOADER_ID, null, this);
}
I also removed initLoader() from onCreate(). This way, every time the fragment is resumed (or created) onStartLoading() will be called. I tried this and it solves my problem.
When you switch from the landscape to portrait or the opposite the Android OS destroy your activity and recreate it again. this what probably trigger your problem

"Already executed" error (Retrofit2 and Android)

First of all, I have 2 fragments and they show different json values(2 links). If I change fragment(.replace()), that fragment get values with retrofit and show it. It works well but other fragment is deleted with values. After change fragment, it downloads again so I change structure. I want to take 2 json objects once so I get json objects in mainactivity and fragments get these with methods. They work well in first opening but if i open a fragment second time, it gives this error. How can I solve it?
java.lang.IllegalStateException: Already executed.
at retrofit2.OkHttpCall.enqueue(OkHttpCall.java:84)
Code is very long, i will show main structure.
MainActivity.java
public class MainActivity extends AppCompatActivity
{
private Call<Restaurant[]> restaurantCall;
private Call<Dining[]> diningCall;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Restaurant Json value
RestaurantInterface restaurantInterface = RetroClient.getClient().create(RestaurantInterface.class);
restaurantCall = restaurantInterface.getJsonValues();
//Dininghall Json value
DiningInterface diningInterface = RetroClient.getClient().create(DiningInterface.class);
diningCall = diningInterface.getJsonValues();
}
public Call<Restaurant[]> RestaurantJson()
{
return this.restaurantCall;
}
public Call<Dining[]> DiningJson()
{
return this.diningCall;
}
}
RestaurantFragment.java (Other fragment has same structure)
public class RestFragment extends Fragment
{
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_rest, container, false);
Call<Restaurant[]> call = ((MainActivity) getActivity()).RestaurantJson();
call.enqueue(new Callback<Restaurant[]>()
{
.
.
Summing up, in order to avoid the "Already executed" exception, clone the call:
public Call<Restaurant[]> RestaurantJson()
{
return this.restaurantCall.clone();
}
public Call<Dining[]> DiningJson()
{
return this.diningCall.clone();
}
If you want to execute the call in the activity and not in the fragment as you are actually doing, then you need to call enqueue(new Callbak(... in your activity.
So you need something like this:
public class RestFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_rest, container, false);
((MainActivity) getActivity()).RestaurantJson(this);
}
public void onResponse(Response<T> response) {
//your fragment code on response
}
...
}
public class MainActivity extends AppCompatActivity {
public void RestaurantJson(final RestFragment fragment)
{
RestaurantInterface restaurantInterface = RetroClient.getClient().create(RestaurantInterface.class);
restaurantCall = restaurantInterface.getJsonValues();
restaurantCall.enqueue(new Callback<Restaurant[]>() {
#Override
public void onResponse(Call<T> call, Response<T> response) {
...
fragment.onResponse(response);
}
}
...
}
The same for your DiningFragment and your dining call...

What kind of code do i have to put in OnCreate() and when do i have to put it in OnCreateView()?

I am trying to understand when i should use the oncreate method or the oncreateview method.
I am a little bit confused. First i had some code including statements like findViewById() in the OnCreate() method. But it always responded with a null pointer Exception, then someone told me i should put it in the OnCreateView() method. It worked, but i do not understand when and what for code i should put in the OnCreate() or when and what i should put in the OnCreateView(). Could someone please explain this to me.
In my code, the methodology is the following:
ACTIVITY code:
#Override
public void onCreate(Bundle saveInstanceState)
{
super.onCreate(saveInstanceState);
this.setContentView(R.layout.activity_container);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (saveInstanceState == null)
{
getSupportFragmentManager().beginTransaction()
.replace(R.id.activity_container_container, new MyFragment()).addToBackStack(null).commit();
}
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener()
{
public void onBackStackChanged()
{
int backCount = getSupportFragmentManager().getBackStackEntryCount();
if (backCount == 0)
{
finish();
}
}
});
}
#Override
public void myInterface()
{
System.out.println("Do stuff;");
}
FRAGMENT code:
private int titleId;
private Button placeholderButton;
private MyInterface activity_myInterface;
public MyFragment()
{
super();
this.titleId = R.string.my_actionbar_title;
}
#Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
try
{
activity_myInterface = (MyInterface)activity;
}
catch(ClassCastException e)
{
Log.e(this.getClass().getSimpleName(), "MyInterface interface needs to be implemented by Activity.", e);
throw e;
}
}
//this is an interface defined by me
#Override
public int getActionBarTitleId()
{
return titleId;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_myfragment, container, false);
placeholderButton = (Button)view.findViewById(R.id.myfragment_placeholderbtn);
placeholderButton.setOnClickListener(this);
return view;
}
#Override
public void onClick(View v)
{
if(v == placeholderButton)
{
System.out.println("Do more stuff");
}
}
Where R.layout.activity_container is:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<FrameLayout
android:id="#+id/activity_container_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
This way the Activity is responsible for containing the Fragment (and this fragment is created in onCreate()) and the Fragment is responsible for the display of UI and the event handling. You can have more than one fragment on an Activity, though, that's what they primarily were designed for.
If you are using an Activity, then you can just stay away from the onCreateView.
Just stick to the default activity lifecycle:
The layout (content) is inflated (created) at the onCreate method.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
}
Then once it has been inflated you are ready to use it and manipulate views that belong to it.
findViewById() returned null for you because the layout was not ready to be used and that's because it was not yet inflated. You can use findViewById() after setContentView() in onCreate.
findViewById() is a method that can be pretty hard on the performance (if called many times), so you should get all the views you need in onCreate and save them to an instance variable like:
TextView textView1, textView2;
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
textView1 = findViewById(R.id.textview1);
textView2 = findViewById(R.id.textview2);
...
}
And use them from onStart:
#Override
protected void onStart() {
super.onStart();
if (textView1 != null) textView1.setText("myText");
if (textView2 != null) textView2.setText("some other text");
}

Test if a layout has been "inflated"

I have an android Activity with swipe navigation (implemented with ViewPager).
public class UberActivity extends FragmentActivity {
ViewPager mViewPager;
protected void onCreate(Bundle savedInstanceState) {
//... some stuff
myfragment1=new MyFragment1();
myfragment2=new MyFragment2();
myfragment3=new MyFragment3();
myfragment4=new MyFragment4();
}
public void onChoiceSelected(){
mViewPager.post(new Runnable(){public void run(){
myfragmen1.update();
myfragmen2.update();
myfragmen3.update();
myfragmen4.update();
}});
}
}
public class Fragment4 extends Fragment {
View v;
#Override
public View onCreateView(LayoutInflater layoutInflater, ViewGroup container,
Bundle savedInstanceState) {
v=layoutInflater.inflate(R.layout.mylayout,container,false);
return v;
}
public void update(){
((TextView)v.findViewById(R.id.textView1)).setText("new text");
}
I will get a NullPointerException on update(), unless I beforehand swipe to it (so that its layout is actually inflated before calling update()).
The problem here is that Fragment4 is instanced but its OnCreateView() is not called. How should I check if OnCreateView() is called? I could put a boolean there, but I don't think this is a best practice...
Just add an if-statement around 'v', checking if it is null or not.
public void update(){
if (v != null) {
((TextView)v.findViewById(R.id.textView1)).setText("new text");
}
}

Categories

Resources