0

I have a simple weather app that consists of a view pager with 3 fragments (Current, Hourly and Daily weather forecast). When I launch my app for the very first time it works fine as well as I close and open it. However, I have noticed that when my app has been in the background processes for a while and then I open it, it crashes with this exception:

java.lang.NullPointerException: Attempt to read from field 'koemdzhiev.com.stormy.weather.Forecast koemdzhiev.com.stormy.ui.MainActivity.mForecast' on a null object reference
at koemdzhiev.com.stormy.ui.Current_forecast_fragment.updateDisplay(Current_forecast_fragment.java:120)
at koemdzhiev.com.stormy.ui.MainActivity$3$3.run(MainActivity.java:234)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5294)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699)

I suspect that this exception has something to do with the fact that I am not saving data using the saveInstance method. This is my current fragment code:

public class Current_forecast_fragment extends Fragment {
private static final String TAG = "MainActivity";
private MainActivity mActivity;
TextView mTimeLabel;
TextView mTemperatureLabel;
TextView mHumidityValue;
TextView mPrecipValue;
TextView mSummaryLabel;
TextView mLocationLabel;
TextView mWindSpeedValue;
TextView mFeelsLike;
ImageView mIconImageView;
ImageView mDegreeImageView;
public SwipeRefreshLayout mSwipeRefreshLayout;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mActivity = ((MainActivity) getActivity());
}

@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.current_forefast_fragment, container, false);
    mTimeLabel = (TextView)v.findViewById(R.id.timeLabel);
    mTemperatureLabel = (TextView)v.findViewById(R.id.temperatureLabel);
    mHumidityValue = (TextView)v.findViewById(R.id.humidityValue);
    mPrecipValue = (TextView)v.findViewById(R.id.precipValue);
    mSummaryLabel = (TextView)v.findViewById(R.id.summaryLabel);
    mLocationLabel = (TextView)v.findViewById(R.id.locationLabel);
    mWindSpeedValue = (TextView)v.findViewById(R.id.windSpeedValue);
    mFeelsLike = (TextView)v.findViewById(R.id.feels_like_label);
    mIconImageView = (ImageView)v.findViewById(R.id.iconImageView);
    mDegreeImageView = (ImageView)v.findViewById(R.id.degreeImageView);
    mSwipeRefreshLayout = (SwipeRefreshLayout)v.findViewById(R.id.current_swipe_refresh_layout);
    mSwipeRefreshLayout.setColorSchemeResources(R.color.orange, R.color.blue, R.color.green);
    mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            Log.d("TAG", "Swiping in current!");
            //if there is internet and if the mSwipeRefreshLayout in the Hourly and daily fragments are not already running...
            if (mActivity.isNetworkAvailable()) {
                if (!mActivity.mHourly_forecast_fragment.mSwipeRefreshLayout.isRefreshing() && !mActivity.mDaily_forecast_fragment.mSwipeRefreshLayout.isRefreshing()) {
                    if (mActivity.isLocationServicesEnabled()) {
                        if (mActivity.latitude != 0.0 && mActivity.longitude != 0.0) {
                            mActivity.getForecast(mActivity.latitude, mActivity.longitude);
                        } else {
                            mActivity.getLocation();
                        }
                    }else{
                        mActivity.alertForNoLocationEnabled();
                    }
                }else{
                    mSwipeRefreshLayout.setRefreshing(false);
                    Toast.makeText(mActivity, "currently refreshing...", Toast.LENGTH_SHORT).show();
                }
            } else {
                Toast.makeText(mActivity, "No Internet Connection!", Toast.LENGTH_LONG).show();
                mSwipeRefreshLayout.setRefreshing(false);
            }
        }
    });
    //Start the swipe refresh layout on start up is internet available
    if(mActivity.isNetworkAvailable())
        mSwipeRefreshLayout.post(new Runnable() {
            @Override
            public void run() {
                mSwipeRefreshLayout.setRefreshing(true);
                Log.d("TAG","running swiping...");
        }
    });

    return v;
}


public void updateDisplay() {
    if(mActivity.mForecast != null) {
        Current current = mActivity.mForecast.getCurrent();
        //setting the current weather details to the ui
        mTemperatureLabel.setText(current.getTemperature() + "");
        mTimeLabel.setText("At " + current.getFormattedTime() + " it is");
        mHumidityValue.setText(current.getHumidity() + "%");
        mPrecipValue.setText(current.getPrecipChange() + "%");
        mSummaryLabel.setText(current.getSummery());
        mWindSpeedValue.setText(current.getWindSpeed() + "");
        mFeelsLike.setText("Feels like: " + current.getFeelsLike());
        mLocationLabel.setText(mActivity.locationName);
        Drawable drawable = ContextCompat.getDrawable(mActivity, current.getIconId());
        mIconImageView.setImageDrawable(drawable);

    }else{
        Toast.makeText(getActivity(),"Could not update data at this time! Please, try again.",Toast.LENGTH_LONG).show();
    }

}

}

Fragment page adapter code:

public class ViewPagerAdapter extends FragmentStatePagerAdapter {
private Current_forecast_fragment mCurrent_forecast_fragment;
private Hourly_forecast_fragment mHourly_forecast_fragment;
private Daily_forecast_fragment mDaily_forecast_fragment;
CharSequence Titles[]; // This will Store the Titles of the Tabs which are Going to be passed when ViewPagerAdapter is created
int NumbOfTabs; // Store the number of tabs, this will also be passed when the ViewPagerAdapter is created


// Build a Constructor and assign the passed Values to appropriate values in the class
public ViewPagerAdapter(FragmentManager fm,CharSequence mTitles[], int mNumbOfTabsumb,Current_forecast_fragment current_fragment,
                        Hourly_forecast_fragment hourly_fragment,
                        Daily_forecast_fragment daily_fragment) {
    super(fm);
    this.mCurrent_forecast_fragment = current_fragment;
    this.mHourly_forecast_fragment = hourly_fragment;
    this.mDaily_forecast_fragment = daily_fragment;
    this.Titles = mTitles;
    this.NumbOfTabs = mNumbOfTabsumb;

}

//This method return the fragment for the every position in the View Pager
@Override
public Fragment getItem(int position) {

    if(position == 0) // if the position is 0 we are returning the First tab
    {
        return this.mCurrent_forecast_fragment;
    }
    else if (position == 1)            // As we are having 2 tabs if the position is now 0 it must be 1 so we are returning second tab
    {
        return this.mHourly_forecast_fragment;
    }else {
        return this.mDaily_forecast_fragment;
    }

}

// This method return the titles for the Tabs in the Tab Strip

@Override
public CharSequence getPageTitle(int position) {
    return Titles[position];
}

// This method return the Number of tabs for the tabs Strip

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

My main activity code:

public class MainActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

    this.mCurrent_forecast_fragment = new Current_forecast_fragment();
    this.mHourly_forecast_fragment = new Hourly_forecast_fragment();
    this.mDaily_forecast_fragment = new Daily_forecast_fragment();

    // Creating The ViewPagerAdapter and Passing Fragment Manager, Titles fot the Tabs and Number Of Tabs.
    adapter = new ViewPagerAdapter(getSupportFragmentManager(), Titles, Numboftabs, mCurrent_forecast_fragment,
            mHourly_forecast_fragment, mDaily_forecast_fragment);

    // Assigning ViewPager View and setting the adapter
    pager = (ViewPager) findViewById(R.id.pager);
    pager.setOffscreenPageLimit(3);
    pager.setAdapter(adapter);

    // Assiging the Sliding Tab Layout View
    tabs = (SlidingTabLayout) findViewById(R.id.tabs);
    tabs.setDistributeEvenly(true); // To make the Tabs Fixed set this true, This makes the tabs Space Evenly in Available width

    // Setting Custom Color for the Scroll bar indicator of the Tab View
    tabs.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() {
        @Override
        public int getIndicatorColor(int position) {
            return ContextCompat.getColor(MainActivity.this, R.color.tabsScrollColor);
        }
    });

    // Setting the ViewPager For the SlidingTabsLayout
    tabs.setViewPager(pager);
}

I have this method in MainActivity which updates the data in the fragments:

public void getForecast(double latitude, double longitude) {
    //scedule no response from the server task...
    mScheduledFuture = exec.schedule(mNotAbleToGetWeatherDataTask,12, TimeUnit.SECONDS);

    Log.d(TAG, "getForecast initiated...");
    String API_KEY = "3ed3a1906736c6f6c467606bd1f91e2c";
    String forecast = "https://api.forecast.io/forecast/" + API_KEY + "/" + latitude + "," + longitude + "?units=si";

    if (isNetworkAvailable()) {

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(forecast)
                .build();

        Call call = client.newCall(request);

        call.enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        toggleSwipeRefreshLayoutsOff();
                    }
                });

            @Override
            public void onResponse(Response response) throws IOException {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        toggleSwipeRefreshLayoutsOff();
                    }
                });
                try {
                    String jsonData = response.body().string();
                    if (response.isSuccessful()) {
                        mForecast = parseForecastDetails(jsonData);
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Log.d(TAG, "isSuccessful - run on UNI threth (update display)...");
//THIS LINE BELLOW CAUSES THE NPE...
                             mCurrent_forecast_fragment.updateDisplay();
                                mHourly_forecast_fragment.setUpHourlyFragment();
                                mDaily_forecast_fragment.setUpDailyFragment();
                                toggleSwipeRefreshLayoutsOff();
                                //set the isFirstTime to true so that the next refresh wont get location
                                isFirstTimeLaunchingTheApp = false;

                            }
                        });


                    } else {
                        alertUserAboutError();
                    }
                } catch (IOException | JSONException e) {
                    Log.e(TAG, "Exception caught:", e);
                }
}
Georgi Koemdzhiev
  • 11,421
  • 18
  • 62
  • 126

1 Answers1

2

Is because mActivity is null.

Add this line in onResume() method

mActivity = ((MainActivity) getActivity());

Like this

@Override
public void onResume() {
    super.onResume();
    mActivity = ((MainActivity) getActivity());
}
Rohit5k2
  • 17,948
  • 8
  • 45
  • 57
  • Hmm, I am going to try that. Thanks. Do you know why my activity has been destroyed by the OS? :) – Georgi Koemdzhiev Jan 16 '16 at 23:14
  • 1
    There could be multiple reasons. This could be nice read for you http://developer.android.com/training/basics/activity-lifecycle/recreating.html – Rohit5k2 Jan 16 '16 at 23:17
  • Thanks, will definitely have a look at that. I will now accept your answer although I have to see if that's gooing to work. – Georgi Koemdzhiev Jan 16 '16 at 23:20
  • 1
    Please let me know if it fixed the issue after your testing. – Rohit5k2 Jan 16 '16 at 23:21
  • Hi, the app still crashes the same way as before with the suggestion you made. :/ – Georgi Koemdzhiev Jan 18 '16 at 20:50
  • Yes, I am calling the updteDisplay method of the mCurrentFragment :) – Georgi Koemdzhiev Jan 19 '16 at 08:02
  • 1
    You are directly calling the method? If yes then please check if fragment is loaded to the activity. If its not loaded try to re-attach the fragment. – Rohit5k2 Jan 19 '16 at 08:09
  • Hmm, are the fragments destroyed by the os after a certain amount of time, is that what is happening ? – Georgi Koemdzhiev Jan 19 '16 at 08:13
  • 1
    I believe when Activity gets destroyed all the attached fragments also gets destroyed, which makes sense as fragments are attached to activities. – Rohit5k2 Jan 19 '16 at 09:10
  • I see, but in this case they should have been recreated in the onCreate method of my activity (I am creating each of the fragments and store them as local variables, then I put them in the adapter) – Georgi Koemdzhiev Jan 19 '16 at 09:12
  • 1
    Unless you attach it to the activity you wont be able to get the `mActivity` because its in `onCreate` of the fragment and this method wont be called unless you load the fragment. – Rohit5k2 Jan 19 '16 at 09:15
  • 1
    Ahh, I understand now, I will reattach the fragment as you suggested above and I think that is going to fix this annoying issue that I had. Appreciate the time you spend helping me, really! :) – Georgi Koemdzhiev Jan 19 '16 at 09:17