15

I have a Fragment with a TableLayout. The data in the table is from a SQLite db. The SQLite db is populated from a RESTful webservice in an AsyncTask in the MainActivity. The Fragment must wait for the task to complete before populating the table. The Fragment listens for the task onPostExecute() method to be called. When it is, the method onLoadAndStoreComplete() in the Fragment is called. This all works.

I need to get a view of a TableLayout outside the OnCreateView() method of a Fragment. If I could get the View of the fragment in onLoadAndStoreComplete that would get me there.

Same code as here. I've got mContext from the MainActivity, but that has no getView() method associated with it.

I've tried:
- making a class member rootView and assigning in onCreateView(), but in onLoadAndStoreComplete(), it is null.
- making a class member tableLayout and assigning in onCreateView(), but in onLoadAndStoreComplete(), it is null.
- calling this.getView() again in onLoadAndStoreComplete(), but it returns null.
- calling the inflator inside onLoadAndStoreComplete(), which works, but then I don't know what to use for container in the .inflate() call

I don't understand why the class member values of rootView and tableLayout are null in onLoadAndStoreComplete()

public class MyFragment extends Fragment implements OnLoadAndStoreCompleteListener {

private TableLayout tableLayout;
private View rootView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  Bundle savedInstanceState) {
        mContext = this.getActivity();
        rootView = inflater.inflate(R.layout.fragment_permits, container, false); // this works
        tableLayout = (TableLayout) rootView.findViewById(R.id.main_table); // and this
        ...
    }

    @Override
    public void onLoadAndStoreComplete() {

       // rootView is null, ie not remembered from OnCreateView

        View view =  getView(); // null

        LayoutInflater inflater = LayoutInflater.from(getActivity());
      rootView = inflater.inflate(R.layout.fragment_permits, container, false); // container? don't know what it should be

        // tableLayout is null
        tableLayout.addView(tableRow, new TableLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
    }
    ...
}
Community
  • 1
  • 1
Al Lelopath
  • 6,448
  • 13
  • 82
  • 139
  • What is `OnLoadAndStoreCompleteListener` and when it is called? Cannot find this in Android API – m0skit0 Apr 13 '15 at 14:30
  • Never heard of `onLoadAndStoreComplete()` are you sure you are respecting the lifecycle of the `Fragment`? If you would then `getView()` would not return `null`... – Xaver Kapeller Apr 13 '15 at 14:31
  • 1
    Also trying to access a `View` from a `Fragment` outside of said `Fragment` is seriously bad design... I suggest you rethink what you want to do and try a more abstract solution. – Xaver Kapeller Apr 13 '15 at 14:33
  • If you will look at the previous question I linked to, you will understand `onLoadAndStoreCompleteListener()`. I am not trying to access the View outside the Fragment, but rather, outside `OnCreateView()` – Al Lelopath Apr 13 '15 at 14:33
  • Your question needs to contain all the information necessary to answer it. You cannot link to another question which explains the code in this question. – Xaver Kapeller Apr 13 '15 at 14:34
  • You should clearly look at this design https://github.com/android10/Android-CleanArchitecture and probably onStore thing executes on another thread – EpicPandaForce Apr 13 '15 at 14:35
  • @Xavier: "You cannot link...". My apologies, I will fix. – Al Lelopath Apr 13 '15 at 14:38
  • @EpicPandaForce: there is not mention of "onStore" in the code at the link you suggest. – Al Lelopath Apr 14 '15 at 21:36
  • Maybe this? http://quabr.com/25697362/android-volley-null-pointer-exception – EpicPandaForce Apr 15 '15 at 05:20
  • Do you have to call your `Asynctask` from `MainActivity`? why not from `MyFragment` – Xcihnegn Apr 22 '15 at 05:12

6 Answers6

11

If I understand your code correctly, there is something wrong either with your code not respecting Fragment lifecycle or a different Fragment instance failure.

A. Lifecycle problem

Android framework expects your Fragment to create its view inside onCreateView() method. View becomes available after framework calls this method.

It is possible that your code called onLoadAndStoreComplete() before onCreateView(), and this has caused the problem.

You need to extract method for populating actual views with data, as it can have 2 entry points. Please see code below:

public class MyFragment extends Fragment implements OnLoadAndStoreCompleteListener {
    private TableLayout tableLayout;
    private View rootView;
    private SomeDataClass loadedData;

    @Override
    public View onCreateView(LayoutInflater inflater, 
                             ViewGroup container,
                             Bundle savedInstanceState) {
        mContext = this.getActivity();
        rootView = inflater.inflate(R.layout.fragment_permits, container, false); 
        tableLayout = (TableLayout) rootView.findViewById(R.id.main_table);

        updateUi(); // If onLoadAndStoreComplete() was already called
                    // this will plug newly created view to returned data
    }

    @Override
    public void onLoadAndStoreComplete() {
        loadedData = ...; // Save loaded data here
        updateUi(); // If onCreateView() was already called
                    // this will plug loaded data to the view
    }


    private void updateUi() {
        if (rootView == null) { // Check if view is already inflated
            return; 
        }

        if (loadedData != null) {
            // View is already inflated and data is ready - update the view!
            ...
        }
    }
}

B. Different instance failure

This may occur when you're operating on 2 different instances of before mentioned fragment.

At first everything will look in order: onCreateView() called before onLoadAndStoreComplete(). Therefore check these:

  • Log/debug default constructor calls for your Fragment. You are expecting to get one call for your fragment during create/load flow.
  • Check whether object that called onCreateView() is the exact same object that called onLoadAndStoreComplete(), you can use System.identityHashCode(this), getting different values within this two methods is a bad sign

If this is what happens, cause is probably hidden a bit deeper, and without code I cannot give you accurate advice on this. How do you create your fragment? Via XML or manually then adding via FragmentManager or via ViewPager?

jskierbi
  • 1,635
  • 1
  • 14
  • 22
  • This is ultimately my problem. I thought the "in the Fragment" meant in my Fragment class, i.e. any method in the class, be it one inherited from `Fragment` or one I created myself. This is incorrect. It means only the methods inhertied from `Fragment`. – Al Lelopath Apr 22 '15 at 13:02
  • save my time with variant B. – danyapd Apr 22 '20 at 14:50
7

getView() gives rootView of Fragment which is returned from onCreatedView(). So if onLoadAndStoreComplete() gets called before onCreatedView() is finished (which it can't return your rootView), you get null since there is not view created yet.


I have tried calling getView() inside onViewCreated() which is NOT null:

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        View viewer=getView();
        if(viewer==null){
            Itu.showToast("null",getActivity(), Toast.LENGTH_LONG);
        }else{
            Itu.showToast(viewer.toString(),getActivity(), Toast.LENGTH_LONG);//This is called
        }
    }
Jemshit
  • 9,501
  • 5
  • 69
  • 106
  • I see your point, but even calling `getView()` in `onStart()` returns null – Al Lelopath Apr 16 '15 at 20:58
  • It is a method from `Fragment`. I added it (ie @Override) in MyFragment as an experiment. – Al Lelopath Apr 16 '15 at 21:06
  • try it inside onViewCreated() which is gets called after onCreateView() – Jemshit Apr 16 '15 at 21:07
  • I did, same thing, so I then tried `onStart()` which is after `onViewCreated()`. Even so, I'm learning that this may not be the way to go because "if you pass data via setters or constructors they will not be restored and therefore can become null in some circumstances." – Al Lelopath Apr 16 '15 at 21:27
  • @AlLelopath I have tried calling `getView()` inside `onViewCreated()` as above edited code, which does NOT give null – Jemshit Apr 16 '15 at 21:33
2

From your code fragment, if fields rootView and tableLayout are initialized after calling onCreateView(..), if both fields are not setted to null after that then they must be not null when you call onLoadAndStoreComplete(..).

Said that, I think you are calling that method in a different instance of your fragment. Review your activity code and check that you are using the same instance from the fragment all the time.

juanhl
  • 1,170
  • 2
  • 11
  • 16
1

I need to get a view of a TableLayout outside the OnCreateView() method of a Fragment. If I could get the View of the fragment in onLoadAndStoreComplete that would get me there.

Try changing the definition of onLoadAndStoreComplete to take a View and then passing in a view of the fragment.

Joshua Byer
  • 524
  • 4
  • 11
  • I've learned that because the Fragment can be killed and restored by the system, if data is passed via setters or constructors they will not be restored and therefore can become null in some circumstances. – Al Lelopath Apr 21 '15 at 13:41
  • @AlLelopath Fragments are no different than activities: use `onSaveInstanceState` in order to save your data into a `Bundle`. In `onViewCreated` you can restore the data again. – tiguchi Apr 21 '15 at 17:52
1

Are you sure that onLoadAndStoreComplete() is called after onCreateView()? The only reason for instance variables to be null is that the method which initializes them is not being called.

I suggest you to put a call to Log.d in onAttach, onDetach, onCreate, onCreateView, onStart, onStop,onLoadAndStoreComplete to see the in which order are called. After that, update your question with the log output to see which could be the problem and probably I could give you a more concise answer.

Adrian
  • 721
  • 2
  • 11
  • 34
1

You can make several fixes :

If the async task is called before the fragment is launched, then your callback onLoadAndStoreComplete may be called before the onCreateView (and all your views are not inflated)

You can execute the asyncTask from onCreateView, but you have to display a loader to tell user that app is syncing

You can also use a listview and not a tableLayout :

public class MyFragment extends Fragment implements OnLoadAndStoreCompleteListener {

private ListView listView;
private View rootView;
private MyAdapter adapter;
private List<T> mDatas; /// This one is populated like you want, from onLoadAndStoreComplete

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,  Bundle savedInstanceState) {
    mContext = this.getActivity();
    rootView = inflater.inflate(R.layout.fragment_permits, container, false); // this works
    listView = (ListView) rootView.findViewById(R.id.main_table); // and this
    mDatas = new List<>();
    adapter = new MyAdapter();
    tableLayout.setAdapter(adapter);
}

@Override
public void onLoadAndStoreComplete() {

   adapter.notifyDataSetChanged();
}


private class MyAdapter extends BaseAdapter{

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return mDatas.size() == 0 ? 1 : mDatas.size();
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return mDatas.get(position);
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (mDatas.size()==0){
            // inflate new view with only a progressbar indefinite 
           LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
           convertView = inflater.inflate(R.layout.row_loading, parent, false);
           return convertView;
        }
        else{
             // list is populated
           LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
           convertView = inflater.inflate(R.layout.row_datas, parent, false);
           // do inflating view stuff
           return convertView;
        }
    }

}

You need to modify your tableLayout to a ListView (inside RelativeLayout?)

Apollo
  • 196
  • 1
  • 9