0

I am just trying to fetch list of earthquakes and making an array list out of it. To avoid multiple API calls on screen rotation, I implemented Mutable Live Data class with ViewModel but by logging in the QueryUtils class where I make the network request, I found out that even when I rotate the device, network request is still being made. I don't understand why. Here are my relevant classes:

public class EarthquakeActivity extends AppCompatActivity {

    private static final String LOG_TAG = "MainActivity";
    private MyModel mMyModel;
    private ViewModelProvider mViewModelProvider;
    private RecyclerView mRecyclerView;
    private TextView mEmptyView;
    private EarthquakeAdapter mEarthquakeAdapter;
    private ProgressBar mProgressBar;
    private ConnectivityManager mConnectivityManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.earthquake_activity);
        mEmptyView = findViewById(R.id.empty_view);
        // get the progress bar to indicate loading
        mProgressBar = findViewById(R.id.loading_bar);
        // set a view model provider for the current activity
        mViewModelProvider = new ViewModelProvider(this);
        // get the view model for the class
        mMyModel = mViewModelProvider.get(MyModel.class);
        // find a reference to the {@link RecyclerView} in the layout
        mRecyclerView = findViewById(R.id.earthquakes);
        mConnectivityManager = getSystemService(ConnectivityManager.class);
        mEmptyView.setText("No internet available");
        mEmptyView.setVisibility(View.VISIBLE);
        Handler handler = new Handler(Looper.getMainLooper());
        mConnectivityManager.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback(){
            @Override
            public void onAvailable(Network network) {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        mEmptyView.setVisibility(View.GONE);
                        mProgressBar.setVisibility(View.VISIBLE);
                        fetchData();
                    }
                });
            }
        });
        Log.e(LOG_TAG,"Visibility changed");
    }

    private void fetchData(){
        // fetch the list of earthquakes
        mMyModel.getEarthquakes().observe(EarthquakeActivity.this, new Observer<ArrayList<Earthquake>>() {
            @Override
            public void onChanged(ArrayList<Earthquake> earthquakes) {
                Log.v(LOG_TAG,"fetching the data");
                // set up recycler view with this data, this will work even if you rotate the device
                setUpRecyclerView(earthquakes);
            }
        });
    }

    private void setUpRecyclerView(ArrayList<Earthquake> earthquakes) {
        if(earthquakes == null || earthquakes.size() == 0) {
            mRecyclerView.setVisibility(View.GONE);
            mProgressBar.setVisibility(View.GONE);
            mEmptyView.setText(R.string.no_data_available);
            mEmptyView.setVisibility(View.VISIBLE);
        }
        else {
            mRecyclerView.setVisibility(View.VISIBLE);
            mEmptyView.setVisibility(View.GONE);
            mProgressBar.setVisibility(View.GONE);
            // create adapter passing in the earthquake data
            mEarthquakeAdapter = new EarthquakeAdapter(EarthquakeActivity.this, earthquakes);
            // attach the adapter to the recyclerView to populate the items
            mRecyclerView.setAdapter(mEarthquakeAdapter);
            // set the layout manager to position the items
            mRecyclerView.setLayoutManager(new LinearLayoutManager(EarthquakeActivity.this));
            Log.e(LOG_TAG,"Recycler view about to be setup");
            // click listener for when an item is clicked
            mEarthquakeAdapter.setClickListener((view, position) -> searchWeb(earthquakes.get(position).getUrl()));
        }
    }
}

My View model class

public class MyModel extends ViewModel {

    private static final String LOG_TAG = "MyModel";
    private MutableLiveData<ArrayList<Earthquake>> mMutableLiveData;

    public MutableLiveData<ArrayList<Earthquake>> getEarthquakes() {
        mMutableLiveData = new MutableLiveData<>();
        // call the API
        init();
        return mMutableLiveData;
    }

    public void init() {
        // perform the network request on separate thread
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                // create array list of earthquakes
                mMutableLiveData.postValue(QueryUtils.fetchEarthquakeData("https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=2021-06-01&limit=300"));
            }
        });
        executorService.shutdown();

    }


}

and my Query Utils class relevant method only since the class is big

private static String makeHttpRequest(URL url) throws IOException {
        String jsonResponse = "";

        // if url null, return
        if(url == null) {
            return jsonResponse;
        }
        HttpURLConnection urlConnection = null;
        InputStream inputStream = null;
        try {
            urlConnection= (HttpURLConnection)url.openConnection();
            urlConnection.setReadTimeout(10000/*milliseconds*/);
            urlConnection.setConnectTimeout(15000/*milliseconds*/);
            urlConnection.setRequestMethod("GET");
            urlConnection.connect();
            // this is being logged again even after screen rotation
            Log.v(LOG_TAG,"Network request made");

            // if the request was successful(response code 200)
            //then read the input stream and parse the output
            if(urlConnection.getResponseCode() == 200) {
                inputStream = urlConnection.getInputStream();
                jsonResponse = readFromStream(inputStream);
            }
            else{
                Log.e(LOG_TAG,"Error Response code: " + urlConnection.getResponseCode());
            }
        }
        catch (IOException e) {
            Log.e(LOG_TAG,"Problem retrieving the earthquake JSON results",e);
        }
        finally {
            if(urlConnection != null) {
                urlConnection.disconnect();
            }
            if(inputStream != null) {
                inputStream.close();
            }
        }
        return jsonResponse;

Please help me on where I am going wrong. This is very confusing for me now.

nmnsharma007
  • 235
  • 3
  • 13

2 Answers2

1

Take a look here, maybe it can help Activity restart on rotation Android

Probably on screen rotation you call onCreate again and so do the ViewModel

Edit: try to call the API in this way

 public MutableLiveData<ArrayList<Earthquake>> getEarthquakes() {
        mMutableLiveData = new MutableLiveData<>();
        //call the API
        mMutableLiveData.init();

cryptex
  • 31
  • 4
  • Yea ok I will try reading that one although I used ViewModel in the first place because it is supposed to be immune to onCreate method call. It only needs to get called again when activity is destroyed completely and then started again.Atleast that's what I had thought – nmnsharma007 Jul 03 '21 at 08:24
  • I am not 100% sure but the rotation is actually calling onDestroy() and then immediately calling onCreate(), I don't think you can call onCreate() without destroying the activity – cryptex Jul 03 '21 at 08:31
  • Yea but viewmodel survives throught the activity lifecycle until the activity is finished. The onDestroy method maybe called again and again but view model survives . That's the whole point of using it – nmnsharma007 Jul 03 '21 at 08:35
  • ViewModel survives until the onDestroy method. If the screen rotation trigger the onDestroy method, the ViewModel doesn't survives. You have to check if this is the problem. – cryptex Jul 03 '21 at 08:39
  • https://developer.android.com/topic/libraries/architecture/viewmodel This article mentions that view model survives the rotation of the screen under the the heading `The lifecycle of a ViewModel` – nmnsharma007 Jul 03 '21 at 08:43
  • the answer u mentioned doesn't work. it gives the error `Cannot resolve method init in Mutable live data` I will try reading the link you sent me then. But if u can still help, please do . – nmnsharma007 Jul 04 '21 at 05:16
  • Also, Now i don't understand what was the point of ViewModel + Live Data if this is still happening . Its so confusing for someone like me who is new and doesn't have anywere else to ask for help :( – nmnsharma007 Jul 04 '21 at 05:20
  • Ok It seems I fixed it. I should read the Documentation more carefully from next time . The documentation mentions that `init()` should only be called when the `MutableLiveData` is null which only happens when activity is first started. Thanks for the help :) – nmnsharma007 Jul 04 '21 at 05:44
1

On config changes, your activity recreates and fetchData() get calls which invokes getEarthquakes() and init().

The steps should be:

  1. Create a private MutableLiveData in viewModel
  2. Create a getter method for that MutableLiveData
  3. in onCreate() method you should register the observer without making any call
  4. Then you should call a public method that fetches data and posts value into MutableLiveData.
  5. This way, your observer in the UI should be triggered.

You should register the observer by calling getEarthquakes() without calling init(). If the data is initially null, you can make a null-check in observer method.

After activity creation you should call a public method created in viewModel that fetches the data, in your case it is init().

Baki Kocak
  • 389
  • 1
  • 9