Skip to content

Instantly share code, notes, and snippets.

@mtali
Last active September 4, 2018 12:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mtali/392d25dc08a5acb36914c032f7995987 to your computer and use it in GitHub Desktop.
Save mtali/392d25dc08a5acb36914c032f7995987 to your computer and use it in GitHub Desktop.
My implementation of AsyncTaskLoader to avoid possible Memory Leak
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.sunshine;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.example.android.sunshine.ForecastAdapter.ForecastAdapterOnClickHandler;
import com.example.android.sunshine.data.SunshinePreferences;
import com.example.android.sunshine.utilities.NetworkUtils;
import com.example.android.sunshine.utilities.OpenWeatherJsonUtils;
import java.lang.ref.WeakReference;
import java.net.URL;
public class MainActivity extends AppCompatActivity implements ForecastAdapterOnClickHandler,
LoaderManager.LoaderCallbacks<String[]> {
private static final String TAG = MainActivity.class.getSimpleName();
private RecyclerView mRecyclerView;
private ForecastAdapter mForecastAdapter;
private TextView mErrorMessageDisplay;
private ProgressBar mLoadingIndicator;
private static final int WEATHER_LOADER_ID = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_forecast);
/*
* Using findViewById, we get a reference to our RecyclerView from xml. This allows us to
* do things like set the adapter of the RecyclerView and toggle the visibility.
*/
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview_forecast);
/* This TextView is used to display errors and will be hidden if there are no errors */
mErrorMessageDisplay = (TextView) findViewById(R.id.tv_error_message_display);
/*
* LinearLayoutManager can support HORIZONTAL or VERTICAL orientations. The reverse layout
* parameter is useful mostly for HORIZONTAL layouts that should reverse for right to left
* languages.
*/
LinearLayoutManager layoutManager
= new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(layoutManager);
/*
* Use this setting to improve performance if you know that changes in content do not
* change the child layout size in the RecyclerView
*/
mRecyclerView.setHasFixedSize(true);
/*
* The ForecastAdapter is responsible for linking our weather data with the Views that
* will end up displaying our weather data.
*/
mForecastAdapter = new ForecastAdapter(this);
/* Setting the adapter attaches it to the RecyclerView in our layout. */
mRecyclerView.setAdapter(mForecastAdapter);
/*
* The ProgressBar that will indicate to the user that we are loading data. It will be
* hidden when no data is loading.
*
* Please note: This so called "ProgressBar" isn't a bar by default. It is more of a
* circle. We didn't make the rules (or the names of Views), we just follow them.
*/
mLoadingIndicator = (ProgressBar) findViewById(R.id.pb_loading_indicator);
getSupportLoaderManager().initLoader(WEATHER_LOADER_ID, null, this);
}
/**
* This method will get the user's preferred location for weather, and then tell some
* background method to get the weather data in the background.
*/
/**
* This method is overridden by our MainActivity class in order to handle RecyclerView item
* clicks.
*
* @param weatherForDay The weather for the day that was clicked
*/
@Override
public void onClick(String weatherForDay) {
Context context = this;
Class destinationClass = DetailActivity.class;
Intent intentToStartDetailActivity = new Intent(context, destinationClass);
intentToStartDetailActivity.putExtra(Intent.EXTRA_TEXT, weatherForDay);
startActivity(intentToStartDetailActivity);
}
/**
* This method will make the View for the weather data visible and
* hide the error message.
* <p>
* Since it is okay to redundantly set the visibility of a View, we don't
* need to check whether each view is currently visible or invisible.
*/
private void showWeatherDataView() {
/* First, make sure the error is invisible */
mErrorMessageDisplay.setVisibility(View.INVISIBLE);
/* Then, make sure the weather data is visible */
mRecyclerView.setVisibility(View.VISIBLE);
}
/**
* This method will make the error message visible and hide the weather
* View.
* <p>
* Since it is okay to redundantly set the visibility of a View, we don't
* need to check whether each view is currently visible or invisible.
*/
private void showErrorMessage() {
/* First, hide the currently visible data */
mRecyclerView.setVisibility(View.INVISIBLE);
/* Then, show the error */
mErrorMessageDisplay.setVisibility(View.VISIBLE);
}
/**
* This method uses the URI scheme for showing a location found on a
* map. This super-handy intent is detailed in the "Common Intents"
* page of Android's developer site:
*
* @see <a"http://developer.android.com/guide/components/intents-common.html#Maps">
* <p>
* Hint: Hold Command on Mac or Control on Windows and click that link
* to automagically open the Common Intents page
*/
private void openLocationInMap() {
String addressString = "1600 Ampitheatre Parkway, CA";
Uri geoLocation = Uri.parse("geo:0,0?q=" + addressString);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(geoLocation);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
} else {
Log.d(TAG, "Couldn't call " + geoLocation.toString()
+ ", no receiving apps installed!");
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
/* Use AppCompatActivity's method getMenuInflater to get a handle on the menu inflater */
MenuInflater inflater = getMenuInflater();
/* Use the inflater's inflate method to inflate our menu layout to this menu */
inflater.inflate(R.menu.forecast, menu);
/* Return true so that the menu is displayed in the Toolbar */
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_refresh) {
invalidateData();
getSupportLoaderManager().restartLoader(WEATHER_LOADER_ID, null, this);
return true;
}
if (id == R.id.action_map) {
openLocationInMap();
return true;
}
return super.onOptionsItemSelected(item);
}
private void invalidateData() {
mForecastAdapter.setWeatherData(null);
}
public static class WeatherLoader extends AsyncTaskLoader<String[]> {
private String[] mWeatherData = null;
private WeakReference<MainActivity> mActivity;
private static final String TAG = WeatherLoader.class.getSimpleName();
WeatherLoader(final MainActivity activity) {
super(activity.getApplicationContext());
mActivity = new WeakReference<>(activity);
}
private MainActivity getActivity() {
return mActivity.get();
}
@Override
protected void onStartLoading() {
if (mWeatherData != null) {
deliverResult(mWeatherData);
} else {
if (getActivity() != null)
getActivity().mLoadingIndicator.setVisibility(View.VISIBLE);
else {
Log.e(TAG, "Failed to retrieve activity reference");
Toast.makeText(getContext(), "Loading . . .", Toast.LENGTH_LONG).show();
}
forceLoad();
}
}
@Override
public String[] loadInBackground() {
String locationQuery = SunshinePreferences.getPreferredWeatherLocation(getContext());
URL weatherRequestUrl = NetworkUtils.buildUrl(locationQuery);
try {
String jsonWeatherResponse = NetworkUtils.getResponseFromHttpUrl(weatherRequestUrl);
return OpenWeatherJsonUtils.getSimpleWeatherStringsFromJson(
getContext(), jsonWeatherResponse
);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public void deliverResult(String[] data) {
mWeatherData = data;
super.deliverResult(data);
}
}
@Override
public Loader<String[]> onCreateLoader(int id, final Bundle args) {
return new WeatherLoader(this);
}
@Override
public void onLoadFinished(Loader<String[]> loader, String[] data) {
mLoadingIndicator.setVisibility(View.INVISIBLE);
mForecastAdapter.setWeatherData(data);
if (null != data) {
showWeatherDataView();
} else {
showErrorMessage();
}
}
@Override
public void onLoaderReset(Loader<String[]> loader) {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment