2

I am currently working on a project in which I have text data retrieved from a web page displayed between 3 different tabs declared as fragments.

There's a button refresh at the bottom of each tab & whenever the user presses that button, the app retrieves the most recent data from the web site & displays it in one of the three tabs.

The logic on which the app chooses the tab to write the text into is the following:

Write data into Tab 1 (leftmost). If Tab 1 already contains data, write into Tab 2. If Tab 2 already contains data, write into Tab 3. If all tabs contain data, overwrite data in Tab 1 & start the cycle over.

Here is my code before getting into my problem. I am only posting the relevant code to avoid flooding this message.

MainActivity:

public class MainActivity extends AppCompatActivity implements 
Tab1.OnFragmentInteractionListener, Tab2.OnFragmentInteractionListener,
    Tab3.OnFragmentInteractionListener{

final static String url = "http://159.203.78.94/rpilog/weatherstation.txt";
public static TextView dataTextTab1;
public static TextView dataTextTab2;
public static TextView dataTextTab3;
static Context context;
public static TabLayout tabLayout;
public static int tabPosition;
public static boolean tab1Used = false;
public static boolean tab2Used = false;
public static boolean tab3Used = false;
public static int positionFactor = -1;
static String TAG;
public static String dataFromURL;

public static TextView test;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //Text placeholders for the data received from the URL.
    dataTextTab1 = (TextView) findViewById(R.id.dataTextTab1);
    dataTextTab2 = (TextView) findViewById(R.id.dataTextTab2);
    dataTextTab3 = (TextView) findViewById(R.id.dataTextTab3);

    context = getApplicationContext();

    //Creating the tabs.
    tabLayout = (TabLayout) findViewById(R.id.tablayout);

    tabLayout.addTab(tabLayout.newTab().setText("No data"));
    tabLayout.addTab(tabLayout.newTab().setText("No data"));
    tabLayout.addTab(tabLayout.newTab().setText("No data"));
    tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);


    //ViewPager & PagerAdapter object to allow sliding tabs.
    final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
    final PagerAdapter adapter = new PagerAdapter(getSupportFragmentManager(), tabLayout.getTabCount());
    viewPager.setAdapter(adapter);
    viewPager.setOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));

    tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
        @Override
        public void onTabSelected(TabLayout.Tab tab) {
            viewPager.setCurrentItem(tab.getPosition());
        }

        @Override
        public void onTabUnselected(TabLayout.Tab tab) {

        }

        @Override
        public void onTabReselected(TabLayout.Tab tab) {

        }
    });


}

public static void receiveString(){
    RequestQueue queue = Volley.newRequestQueue(context);

    //Requests a string response from the provided URL.
    StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
            new Response.Listener<String>() {
                @Override
                public void onResponse(String response) {

                    dataFromURL = response;

                    if (positionFactor * 169 + 16 + 3 + 144 <= response.length())  {

                        if(tab1Used == false) {
                            tabPosition = 0;
                            TabLayout.Tab currentTab = tabLayout.getTabAt(tabPosition);
                            currentTab.setText(response.substring(positionFactor * 169, positionFactor * 169 + 16));
                            dataTextTab1.setText(response.substring(positionFactor * 169 + 16 + 3, positionFactor * 169 + 16 + 3 + 144));
                            tab1Used = true;
                            Log.e(TAG, "1ST TAB REACHED");

                        }

                        else if(tab1Used == true && tab2Used == false){
                            tabPosition = 1;
                            TabLayout.Tab currentTab = tabLayout.getTabAt(tabPosition);
                            currentTab.setText(response.substring(positionFactor * 169, positionFactor * 169 + 16));
                            dataTextTab2.setText(response.substring(positionFactor * 169 + 16 + 3, positionFactor * 169 + 16 + 3 + 144));
                            tab2Used = true;
                            Log.e(TAG, "2ND TAB REACHED");
                        }

                        else if(tab1Used == true && tab2Used == true && tab3Used == false){
                                tabPosition = 2;
                                TabLayout.Tab currentTab = tabLayout.getTabAt(tabPosition);
                                currentTab.setText(response.substring(positionFactor * 169, positionFactor * 169 + 16));
                                dataTextTab3.setText(response.substring(positionFactor * 169 + 16 + 3, positionFactor * 169 + 16 + 3 + 144));
                                tab3Used = true;
                                Log.e(TAG, "3RD TAB REACHED");
                        }

                        if(tab1Used == true && tab2Used == true && tab3Used == true){  //If there's data in all tabs => overwrite oldest.
                            tabPosition = 0;
                            TabLayout.Tab currentTab = tabLayout.getTabAt(tabPosition);
                            currentTab.setText(response.substring(positionFactor * 169, positionFactor * 169 + 16));
                            dataTextTab1.setText(response.substring(positionFactor * 169 + 16 + 3, positionFactor * 169 + 16 + 3 + 144));
                            Log.e(TAG, "1ST TAB OVER AGAIN");
                            tab2Used = false;
                            tab3Used = false;

                        }
                    }


                }



            }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            Toast.makeText(context, "Error in data retrieval", Toast.LENGTH_LONG).show();
        }
    });

    queue.add(stringRequest);

}


public void refresh(View view){             //Refresh action for FAB = onClick fct.

    MainActivity.positionFactor++;
    Toast.makeText(context, "Page refreshed", Toast.LENGTH_SHORT).show();
    receiveString();

}

@Override
public void onFragmentInteraction(Uri uri) {

}

public static void showInfoPopup() {        //Show popup info = onClick fct.

    if (Menu.infoPopupDialog != null) {
        Menu.infoPopupDialog.setContentView(R.layout.popup_layout);

        Menu.infoPopupDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        Menu.infoPopupDialog.show();
    }
}

public static void showLocationPopup(){     //Show popup location = onClick fct.

    if(Menu.locationPopupDialog != null){
        Menu.locationPopupDialog.setContentView(R.layout.popup_location);

        Menu.locationPopupDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        Menu.locationPopupDialog.show();
    }
}

PagerAdapter:

public class PagerAdapter extends FragmentStatePagerAdapter {

int mNoOfTabs;

public PagerAdapter(FragmentManager fm, int numberOfTabs){

    super(fm);
    this.mNoOfTabs = numberOfTabs;

}



@Override
public Fragment getItem(int position) {

    switch(position){

        case 0:
            Tab1 tab1 = new Tab1();
            return tab1;
        case 1:
            Tab2 tab2 = new Tab2();
            return tab2;
        case 2:
            Tab3 tab3 = new Tab3();
            return tab3;
        default:
            return null;

    }

}

@Override
public int getCount() {
    return mNoOfTabs;
}

}

Tab1:

public class Tab1 extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";

// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;


private OnFragmentInteractionListener mListener;

public Tab1() {
    // Required empty public constructor
}

/**
 * Use this factory method to create a new instance of
 * this fragment using the provided parameters.
 *
 * @param param1 Parameter 1.
 * @param param2 Parameter 2.
 * @return A new instance of fragment Tab1.
 */
// TODO: Rename and change types and number of parameters
public static Tab1 newInstance(String param1, String param2) {
    Tab1 fragment = new Tab1();
    Bundle args = new Bundle();
    args.putString(ARG_PARAM1, param1);
    args.putString(ARG_PARAM2, param2);
    fragment.setArguments(args);
    return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
        mParam1 = getArguments().getString(ARG_PARAM1);
        mParam2 = getArguments().getString(ARG_PARAM2);

    }


}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View rootView = inflater.inflate(R.layout.fragment_tab1, container, false);

    MainActivity.dataTextTab1 = rootView.findViewById(R.id.dataTextTab1);


    return rootView;
}


// TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
    if (mListener != null) {
        mListener.onFragmentInteraction(uri);
    }
}

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnFragmentInteractionListener) {
        mListener = (OnFragmentInteractionListener) context;
    } else {
        throw new RuntimeException(context.toString()
                + " must implement OnFragmentInteractionListener");
    }
}

@Override
public void onDetach() {
    super.onDetach();
    mListener = null;
}

/**
 * This interface must be implemented by activities that contain this
 * fragment to allow an interaction in this fragment to be communicated
 * to the activity and potentially other fragments contained in that
 * activity.
 * <p>
 * See the Android Training lesson <a href=
 * "http://developer.android.com/training/basics/fragments/communicating.html"
 * >Communicating with Other Fragments</a> for more information.
 */
public interface OnFragmentInteractionListener {
    // TODO: Update argument type and name
    void onFragmentInteraction(Uri uri);
  }
}

Tab2:

public class Tab2 extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";

// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;

private OnFragmentInteractionListener mListener;

public Tab2() {
    // Required empty public constructor
}

/**
 * Use this factory method to create a new instance of
 * this fragment using the provided parameters.
 *
 * @param param1 Parameter 1.
 * @param param2 Parameter 2.
 * @return A new instance of fragment Tab2.
 */
// TODO: Rename and change types and number of parameters
public static Tab2 newInstance(String param1, String param2) {
    Tab2 fragment = new Tab2();
    Bundle args = new Bundle();
    args.putString(ARG_PARAM1, param1);
    args.putString(ARG_PARAM2, param2);
    fragment.setArguments(args);
    return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
        mParam1 = getArguments().getString(ARG_PARAM1);
        mParam2 = getArguments().getString(ARG_PARAM2);
    }
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View rootView = inflater.inflate(R.layout.fragment_tab2, container, false);

    MainActivity.dataTextTab2 = rootView.findViewById(R.id.dataTextTab2);
    return rootView;
}

// TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
    if (mListener != null) {
        mListener.onFragmentInteraction(uri);
    }
}

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnFragmentInteractionListener) {
        mListener = (OnFragmentInteractionListener) context;
    } else {
        throw new RuntimeException(context.toString()
                + " must implement OnFragmentInteractionListener");
    }
}

@Override
public void onDetach() {
    super.onDetach();
    mListener = null;
}

/**
 * This interface must be implemented by activities that contain this
 * fragment to allow an interaction in this fragment to be communicated
 * to the activity and potentially other fragments contained in that
 * activity.
 * <p>
 * See the Android Training lesson <a href=
 * "http://developer.android.com/training/basics/fragments/communicating.html"
 * >Communicating with Other Fragments</a> for more information.
 */
public interface OnFragmentInteractionListener {
    // TODO: Update argument type and name
    void onFragmentInteraction(Uri uri);
 }
}

Tab3:

public class Tab3 extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";

// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;

private OnFragmentInteractionListener mListener;

public Tab3() {
    // Required empty public constructor
}

/**
 * Use this factory method to create a new instance of
 * this fragment using the provided parameters.
 *
 * @param param1 Parameter 1.
 * @param param2 Parameter 2.
 * @return A new instance of fragment Tab3.
 */
// TODO: Rename and change types and number of parameters
public static Tab3 newInstance(String param1, String param2) {
    Tab3 fragment = new Tab3();
    Bundle args = new Bundle();
    args.putString(ARG_PARAM1, param1);
    args.putString(ARG_PARAM2, param2);
    fragment.setArguments(args);
    return fragment;
}



@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
        mParam1 = getArguments().getString(ARG_PARAM1);
        mParam2 = getArguments().getString(ARG_PARAM2);
    }

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View rootView = inflater.inflate(R.layout.fragment_tab3, container, false);

    MainActivity.dataTextTab3 = rootView.findViewById(R.id.dataTextTab3);

    return rootView;
}

// TODO: Rename method, update argument and hook method into UI event
public void onButtonPressed(Uri uri) {
    if (mListener != null) {
        mListener.onFragmentInteraction(uri);
    }
}

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnFragmentInteractionListener) {
        mListener = (OnFragmentInteractionListener) context;
    } else {
        throw new RuntimeException(context.toString()
                + " must implement OnFragmentInteractionListener");
    }
}

@Override
public void onDetach() {
    super.onDetach();
    mListener = null;
}

/**
 * This interface must be implemented by activities that contain this
 * fragment to allow an interaction in this fragment to be communicated
 * to the activity and potentially other fragments contained in that
 * activity.
 * <p>
 * See the Android Training lesson <a href=
 * "http://developer.android.com/training/basics/fragments/communicating.html"
 * >Communicating with Other Fragments</a> for more information.
 */
public interface OnFragmentInteractionListener {
    // TODO: Update argument type and name
    void onFragmentInteraction(Uri uri);
 }

}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent"
    tools:context="com.myapps.toualbiamine.weathertracker.MainActivity">

    <!--<android.support.v7.widget.Toolbar-->
        <!--android:id="@+id/toolbar"-->
        <!--android:layout_width="match_parent"-->
        <!--android:layout_height="50dp"-->
        <!--android:background="@color/colorPrimary"-->
        <!--android:minHeight="?attr/actionBarSize"-->
        <!--android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"-->
        <!--app:popupTheme="@style/ThemeOverlay.AppCompat.Light">-->

        <!--<TextView-->
            <!--android:layout_width="wrap_content"-->
            <!--android:layout_height="wrap_content"-->
            <!--android:text="Swipe to switch tabs"-->
            <!--android:textSize="30dp"/>-->

    <!--</android.support.v7.widget.Toolbar>-->

    <android.support.design.widget.TabLayout
        android:id="@+id/tablayout"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:background="@color/textColor"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

    </android.support.design.widget.TabLayout>

    <android.support.v4.view.ViewPager
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:id="@+id/pager"
        android:background="@color/background">


    </android.support.v4.view.ViewPager>


</LinearLayout>

fragment_tab3.xml - fragment_tab1.xml & fragment_tab2.xml are simple copies:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.myapps.toualbiamine.weathertracker.Tab1"
    android:orientation="vertical"
    android:layout_weight="1"
    android:background="@drawable/bg"
    android:backgroundTintMode="multiply"
    android:backgroundTint="@color/background">

    <!-- TODO: Update blank fragment layout -->

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Data"
        android:textSize="40dp"
        android:layout_gravity="center"
        android:layout_marginTop="40dp"
        android:textColor="@color/titleColor" />

    <TextView
        android:id="@+id/dataTextTab3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="120dp"
        android:text="Data will come here"
        android:textSize="15dp"
        android:layout_gravity="center"
        />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.FloatingActionButton
            android:id="@+id/floatingActionButton4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentEnd="true"
            android:layout_alignParentRight="true"
            android:layout_marginBottom="28dp"
            android:layout_marginEnd="16dp"
            android:layout_marginRight="16dp"
            android:clickable="true"
            app:backgroundTint="@color/background"
            app:fabSize="normal"
            app:srcCompat="@drawable/ic_refresh_black_24dp"
            android:onClick="refresh"/>
    </RelativeLayout>

</LinearLayout>

So, I've been trying to figure out a way around my problem & have done a lot of reading but I've been stuck here for now a week & it's the last part of my project.

Here is the issue. You can see that in my MainActivity class, in the method receiveString(), there is the following line:

dataTextTab3.setText(response.substring(positionFactor * 169 + 16 + 3, positionFactor * 169 + 16 + 3 + 144));

I used the same line for my two other TextViews dataTextTab1 & dataTextTab2 a few lines before in this same method.

This is the part where I parse the data that I want received from the website. The numbers correspond to a formula to determine the indexes of the substring I set the text to.

It works perfectly fine for my two other TextViews but when I reach the third TextView, dataTextTab3, I get a NullPointerException & I can't get my head around it to figure it out.

StackTrace:

E/AndroidRuntime: FATAL EXCEPTION: main Process: com.myapps.toualbiamine.weathertracker, PID: 22340 java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
at com.myapps.toualbiamine.weathertracker.MainActivity$2. onResponse(MainActivity.java:139) at com.myapps.toualbiamine.weathertracker.MainActivity$2 .onResponse(MainActivity.java:108) at com.android.volley.toolbox.StringRequest. deliverResponse(StringRequest.java:60) at com.android.volley.toolbox.StringRequest. deliverResponse(StringRequest.java:30) at com.android.volley.ExecutorDelivery$ResponseDeliveryRunnable. run(ExecutorDelivery.java:99) at android.os.Handler.handleCallback(Handler.java:790) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6494) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller. run(RuntimeInit.java:438) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

One more thing, I get that error if I refresh 3 times from the first tab to logically fill all the tabs at once, however, if I refresh the first tab, then go to the second one & refresh it, and then go to the third one & refresh it, I do not get the NullPointerException.

Is there a way to avoid that NPE whatever happens? I would like to make sure that the user can refresh 3 times from the first tab without having the app crashing.

Thank you very much, Community! Please, I place a lot of hope into you right now, be the hero I need.

PSA: not duplicate of "why am I getting a NPE" since I went through most of the posts & I am explicitly declaring and instantiating my variables.

ChanceVI
  • 200
  • 1
  • 10
  • Possible duplicate of [What is a NullPointerException, and how do I fix it?](https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it) – Logan Jul 12 '18 at 02:40
  • 1
    No, I know how to work around a regular NPE, or how to declare & instantiate a variable. This is not the problem here. – ChanceVI Jul 12 '18 at 02:41
  • Have you checked what exactly you are getting in __dataTextTab3 = (TextView) findViewById(R.id.dataTextTab3);__ while debugging – Aman Chhabra Jul 12 '18 at 02:41
  • @AmanChhabra, I am getting a null reference because the TextView is not created, even if I instantiate & properly declare everything. – ChanceVI Jul 12 '18 at 02:44

1 Answers1

2

When you start the MainActivity, typically it will instantiate the visible Fragment (tab1) and the next one over (tab2) but not beyond that until you switch tabs. That happens later, after you have already called onCreate. That means that in your onCreate you are getting null for dataTextTab3

You shouldn't be holding references in your activity to views in Fragments, since as they are not guaranteed to exist all the time.

If you want to force them to exist, you can call viewPager.setOffscreenPageLimit(3); to allow more off-screen fragments to be instantiated at startup.

Tyler V
  • 9,694
  • 3
  • 26
  • 52
  • Is there a way to force the creation of `dataTextTab3` when the first tab is created, even if the user has to wait a little bit? – ChanceVI Jul 12 '18 at 02:43
  • 1
    You could try setting `viewPager.setOffscreenPageLimit(3);` I don't know if that forces it to instantiate more offscreen pages automatically or not though. – Tyler V Jul 12 '18 at 02:45
  • Brother, you're the man. Solved it. It magically forces all the fragments to be instantiated & I can now access my variable. Thank you so much. – ChanceVI Jul 12 '18 at 02:54
  • 1
    Glad it helped. I edited the answer to include that fix too. – Tyler V Jul 12 '18 at 02:58