When I press back button my app is crashed with this message: photogallery app has stopped


#1

Logcat error shows:
5-09 21:11:34.054 24087-24087/com.example.imran.photogallery E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.imran.photogallery, PID: 24087
java.lang.IllegalStateException: Fragment PhotoGalleryFragment{4a4ec3f} not attached to a context.
at android.support.v4.app.Fragment.requireContext(Fragment.java:614)
at android.support.v4.app.Fragment.getResources(Fragment.java:678)
at com.example.imran.photogallery.PhotoGalleryFragment$1.onThumbnailDownloaded(PhotoGalleryFragment.java:52)
at com.example.imran.photogallery.PhotoGalleryFragment$1.onThumbnailDownloaded(PhotoGalleryFragment.java:49)
at com.example.imran.photogallery.ThumbnailDownloader$2.run(ThumbnailDownloader.java:89)
at android.os.Handler.handleCallback(Handler.java:746)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5491)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:728)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)


#2

Is there anyone to figure out this problem???


#3

Maybe I can help you but I need more information. I see that your fragment is not attached to a context.

So, can you paste both the hosting activity and PhotoGalleryFragment.java class here?


#4

Thumbnail Downloader.java

package com.example.imran.photogallery;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;

import java.io.IOException;
import java.lang.annotation.Target;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**

  • Created by Imran on 8/5/2018.
    */

public class ThumbnailDownloader extends HandlerThread {

private static final String TAG = "ThumbnailDownloader";
private static final int MESSAGE_DOWNLOAD = 0;

private Handler mRequestHandler;
private ConcurrentMap<T, String> mRequestMap = new ConcurrentHashMap<>();
private Handler mResponseHandler;
private ThumbnailDownloadListener<T> mThumbnailDownloadListener;

public interface ThumbnailDownloadListener<T>{
    void onThumbnailDownloaded(T target, Bitmap thumbnail);
}

public void setThumbnailDownloadListener(ThumbnailDownloadListener<T> listener) {
    mThumbnailDownloadListener = listener;
}

public ThumbnailDownloader(Handler responseHandler){
    super(TAG);
    mResponseHandler = responseHandler;
}

public void queueThumbnail(T target, String url){
    Log.i(TAG, "Got a URL: " + url);

    if(url == null){
        mRequestMap.remove(target);
    }else{
        mRequestMap.put(target, url);
        mRequestHandler.obtainMessage(MESSAGE_DOWNLOAD, target)
                .sendToTarget();
    }
}

@Override
protected void onLooperPrepared() {
    mRequestHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if(msg.what == MESSAGE_DOWNLOAD){
                T target = (T) msg.obj;
                Log.i(TAG, "Got a request for URL: " + mRequestMap.get(target));
                handleRequest(target);
            }
        }
    };
}

public void clearQueue(){
    mRequestHandler.removeMessages(MESSAGE_DOWNLOAD);
}
private void handleRequest(final T target){
    try{
        final String url = mRequestMap.get(target);

        if(url == null){
            return;
        }
        byte[] bitmapBytes = new FlickrFetcher().getUrlBytes(url);
        final Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
        Log.i(TAG, "Bitmap Created");

        mResponseHandler.post(new Runnable() {
            @Override
            public void run() {
                if(mRequestMap.get(target) != url){
                    return;
                }
                mRequestMap.remove(target);
                mThumbnailDownloadListener.onThumbnailDownloaded(target, bitmap);

            }
        });

    }catch (IOException ioe){
        Log.e(TAG, "Error downloading image ", ioe);
    }

}

}


#5

PhotoGalleryActivity.java

package com.example.imran.photogallery;

import android.content.Context;
import android.content.Intent;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class PhotoGalleryActivity extends SingleFragmentActivity {

public static Intent newIntent(Context context){
    return new Intent(this, PhotoGalleryActivity.class);
}

@Override
protected Fragment createFragment() {
    return PhotoGalleryFragment.newInstance();
}

}


#6

FlickrFetcher.java

package com.example.imran.photogallery;

import android.net.Uri;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**

  • Created by Imran on 7/5/2018.
    */

public class FlickrFetcher {

private static final String TAG = "FlickrFetcher";
private static final String FETCH_RECENT_METHOD = "flickr.photos.getRecent";
private static final String SEARCH_METHOD = "flickr.photos.search";
private static final String API_KEY = "624bded3e3c6e29d914ad149ac1c0bea";
private static final Uri ENDPOINT = Uri
        .parse("https://api.flickr.com/services/rest/")
        .buildUpon()
        .appendQueryParameter("api_key", API_KEY)
        .appendQueryParameter("format", "json")
        .appendQueryParameter("nojsoncallback", "1")
        .appendQueryParameter("extras", "url_s")
        .build();

public byte[] getUrlBytes(String urlSpecs) throws IOException{
    URL url = new URL(urlSpecs);
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();

    try{
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        InputStream input = connection.getInputStream();

        if(connection.getResponseCode() != HttpURLConnection.HTTP_OK){
            throw new IOException(connection.getResponseMessage() + ": with " + urlSpecs);
        }
        int bytesRead = 0;
        byte[] buffer = new byte[1024];
        while((bytesRead = input.read(buffer)) > 0){
            output.write(buffer, 0, bytesRead);
        }
        output.close();
        return output.toByteArray();
    }finally {
        connection.disconnect();
    }
}

public String getUrlString(String urlSpecs) throws IOException{
    return new String(getUrlBytes(urlSpecs));
}

private List<GalleryItem> downlaodGalleryItems(String url){

    List<GalleryItem> items = new ArrayList<>();
    try{
        String jsonString = getUrlString(url);
        Log.i(TAG, "Received Json: " + jsonString);
        JSONObject jsonObject = new JSONObject(jsonString);
        parseItems(items, jsonObject);
    }catch (JSONException je) {
        Log.e(TAG, "Failed to parse JSON: ", je);
    } catch (IOException ioe){
        Log.e(TAG, "Failed to fetch items: ", ioe);
    }
    return items;
}

private void parseItems(List<GalleryItem> items, JSONObject jsonBody)
        throws IOException, JSONException{

    JSONObject photosJsonObject = jsonBody.getJSONObject("photos");
    JSONArray photosJsonArray = photosJsonObject.getJSONArray("photo");

    for(int i = 0; i < photosJsonArray.length(); i++){
        JSONObject photosJsonObj = photosJsonArray.getJSONObject(i);

        GalleryItem item = new GalleryItem();
        item.setId(photosJsonObj.getString("id"));
        item.setCaption(photosJsonObj.getString("title"));

        if(!photosJsonObj.has("url_s")){
            continue;
        }
        item.setUrl(photosJsonObj.getString("url_s"));
        items.add(item);
    }
}
private String buildUrl(String method, String query){
    Uri.Builder uriBuilder = ENDPOINT.buildUpon()
            .appendQueryParameter("method", method);

    if(method.equals(SEARCH_METHOD)){
        uriBuilder.appendQueryParameter("text", query);
    }
    return uriBuilder.build().toString();
}
public List<GalleryItem> fetchRecentPhotos(){
    String url = buildUrl(FETCH_RECENT_METHOD, null);
    return downlaodGalleryItems(url);
}

public List<GalleryItem> searchPhotos(String query){
    String url = buildUrl(SEARCH_METHOD, query);
    return downlaodGalleryItems(url);
}

}


#7

SingleFragmentActivity.java

package com.example.imran.photogallery;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

/**

  • Created by Imran on 7/5/2018.
    */

public abstract class SingleFragmentActivity extends AppCompatActivity {
protected abstract Fragment createFragment();

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

    FragmentManager fm = getSupportFragmentManager();
    Fragment fragment = fm.findFragmentById(R.id.fragment_container);

    if(fragment == null){
        fragment = createFragment();
        fm.beginTransaction()
                .add(R.id.fragment_container, fragment)
                .commit();
    }
}

}


#8

PhotoGalleryFragment.java

package com.example.imran.photogallery;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**

  • Created by Imran on 7/5/2018.
    */

public class PhotoGalleryFragment extends Fragment {
public static final String TAG = “PhotoGalleryFragment”;
RecyclerView mPhotoRecyclerView;
private List mItems = new ArrayList<>();
private ThumbnailDownloader mThumbnailDownloader;

public static PhotoGalleryFragment newInstance(){
    return new PhotoGalleryFragment();
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(true);
    setHasOptionsMenu(true);
    updateItems();



    Handler responseHandler = new Handler();
    mThumbnailDownloader = new ThumbnailDownloader<>(responseHandler);
    mThumbnailDownloader.setThumbnailDownloadListener(
            new ThumbnailDownloader.ThumbnailDownloadListener<PhotoHolder>() {
        @Override
        public void onThumbnailDownloaded(PhotoHolder target, Bitmap thumbnail) {
            Drawable drawable = new BitmapDrawable(getResources(), thumbnail);
            target.bindGalleryItem(drawable);
        }
    });

    mThumbnailDownloader.start();
    mThumbnailDownloader.getLooper();
    Log.e(TAG, "Background thread started");
}

@Override
public void onDestroy() {
    super.onDestroy();
    mThumbnailDownloader.quit();
    Log.i(TAG, "Background thread destroyed");
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    mThumbnailDownloader.clearQueue();
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
    inflater.inflate(R.menu.fragment_photo_search, menu);

    MenuItem searchItem = menu.findItem(R.id.menu_item_search);
    final SearchView searchView = (SearchView) searchItem.getActionView();
    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            Log.d(TAG, "QueryTextSubmit: " + query);
            QueryPreferences.setStoredQuery(getActivity(), query);
            updateItems();
            return true;
        }

        @Override
        public boolean onQueryTextChange(String newText) {
            Log.d(TAG, "QueryTextChange: " + newText);
            return false;
        }
    });
    searchView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            String query = QueryPreferences.getStoredQuery(getActivity());
            searchView.setQuery(query, false);
        }
    });
    MenuItem toggleItem = menu.findItem(R.id.menu_item_toggle_polling);
    if(IntentBackgroundService.isServiceAlarmOn(getActivity())){
        toggleItem.setTitle(R.string.stop_polling);
    }else{
        toggleItem.setTitle(R.string.start_polling);
    }
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()){
        case R.id.menu_item_clear:
            QueryPreferences.setStoredQuery(getActivity(), null);
            updateItems();
            return true;

        case R.id.menu_item_toggle_polling:
            boolean shouldStartAlarm = !IntentBackgroundService.isServiceAlarmOn(getActivity());
            IntentBackgroundService.setServiceAlarm(getActivity(), shouldStartAlarm);
            getActivity().invalidateOptionsMenu();
            return true;


        default:
            return super.onOptionsItemSelected(item);

    }
}

private void updateItems() {
    String query = QueryPreferences.getStoredQuery(getActivity());
    new FetchItemsTasks(query).execute();
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_photo_gallery, container, false);

    mPhotoRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view_photo_gallery);
    mPhotoRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3));
    setupAdapter();
    return view;
}

private void setupAdapter() {
    if(isAdded()){
        mPhotoRecyclerView.setAdapter(new PhotoAdapter(mItems));
    }
}


private class FetchItemsTasks extends AsyncTask<Void, Void, List<GalleryItem>>{
    private String query;

    public FetchItemsTasks(String query){
        this.query = query;
    }

    @Override
    protected List<GalleryItem> doInBackground(Void... voids) {

        if(query == null){
            return new FlickrFetcher().fetchRecentPhotos();
        }else{
            return new FlickrFetcher().searchPhotos(query);
        }

    }

    @Override
    protected void onPostExecute(List<GalleryItem> items) {
        mItems = items;
        setupAdapter();
    }
}

private class PhotoHolder extends RecyclerView.ViewHolder{
    private ImageView mImageView;

    public PhotoHolder(View itemView){
        super(itemView);
        mImageView = (ImageView) itemView.findViewById(R.id.fragment_image_view);
    }
    public void bindGalleryItem(Drawable drawable){
        mImageView.setImageDrawable(drawable);
    }
}

private class PhotoAdapter extends RecyclerView.Adapter<PhotoHolder>{

    private List<GalleryItem> mitems;

    public PhotoAdapter(List<GalleryItem> items){
        mitems = items;
    }

    @NonNull
    @Override
    public PhotoHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(getActivity());
        View view = inflater.inflate(R.layout.layout_gallery_item, parent, false);
        return new PhotoHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull PhotoHolder holder, int position) {
        GalleryItem galleryItem = mitems.get(position);
        mThumbnailDownloader.queueThumbnail(holder, galleryItem.getUrl());
    }

    @Override
    public int getItemCount() {
        return mitems.size();
    }
}

}


#9

Replace the above line from the PhotoGalleryFragment.java file with:

Drawable drawable = new BitmapDrawable(getActivity().getResources(), thumbnail);

I think that’ll solve the issue.


#10

There is another error occurred:

05-14 22:52:55.621 13028-13028/com.example.imran.photogallery E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.imran.photogallery, PID: 13028
java.lang.NullPointerException: Attempt to invoke virtual method ‘android.content.res.Resources android.support.v4.app.FragmentActivity.getResources()’ on a null object reference
at com.example.imran.photogallery.PhotoGalleryFragment$1.onThumbnailDownloaded(PhotoGalleryFragment.java:62)
at com.example.imran.photogallery.PhotoGalleryFragment$1.onThumbnailDownloaded(PhotoGalleryFragment.java:59)
at com.example.imran.photogallery.ThumbnailDownloader$2.run(ThumbnailDownloader.java:89)
at android.os.Handler.handleCallback(Handler.java:746)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5491)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:728)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)


#11

May I know the action that you performed just before the error occurred?


#12

The problem is that the downloaded bitmap is being sent to your fragment after the activity is destroyed. Since your fragment is no longer attached to a context, getResources() will throw an error (and getActivity() will return null).

In this situation, you don’t have any reason to update the thumbnails anyway. At the beginning of your onThumbnailDownloaded method, add the following line:

if (!isAdded()) return;

This will return from the method immediately if the fragment is no longer attached.