Reputation: 1542
When I rotate my device, the information contained within my fragment in my app is reloaded. Since this app pulls information from the internet, this means, a lack of data for some time while data is being recollected.
I have read many articles and S/O questions that give a few general pointers and have tried their solutions, from android:configChanges="orientation|screenSize"
in the manifest to adding setRetainInstance(true);
in both my MainActivity
and FragmentActivity
. Neither of these have worked to keep my fragment from reloading. I used the android:configChanges
in another activity in the app (A detail activity, which activates when a user selects a news story) and it works properly there, preventing the activity from reloading.
This is a simple app that pulls data from the "Hacker News" API and shows it to the user. There is a NavigationDrawer for the user to select different news types. Each item in the NavigationDrawer is a different Fragment that loads in the MainActivity.class
. The Fragment in question here is HomeFragment.class
which loads the main news stories.
I can't seem to find anything that works with the structure of my app as it currently stands, so I would like some help in trying to find a solution for this seemingly trivial (but confusing) problem.
package com.material.tdapps.hackernews.activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import com.material.tdapps.hackernews.R;
public class MainActivity extends AppCompatActivity implements FragmentDrawer.FragmentDrawerListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar appToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(appToolbar);
//noinspection ConstantConditions
getSupportActionBar().setDisplayShowHomeEnabled(true);
FragmentDrawer drawerFragment = (FragmentDrawer) getSupportFragmentManager().findFragmentById(R.id.fragment_navigation_drawer);
drawerFragment.setUp(R.id.fragment_navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout), appToolbar);
drawerFragment.setDrawerListener(this);
displayView(0);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
return id == R.id.action_settings || super.onOptionsItemSelected(item);
}
@Override
public void onDrawerItemSelected(View view, int position) {
displayView(position);
}
private void displayView(int position){
Fragment fragment = null;
String title = getString(R.string.app_name);
switch (position) {
case 0:
fragment = new HomeFragment();
title = getString(R.string.title_news);
break;
case 1:
fragment = new ShowFragment();
title = getString(R.string.title_show);
break;
case 2:
fragment = new AskFragment();
title = getString(R.string.title_ask);
break;
case 3:
fragment = new JobsFragment();
title = getString(R.string.title_job);
break;
default:
break;
}
if (fragment != null){
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.container_body, fragment);
fragmentTransaction.commit();
getSupportActionBar().setTitle(title);
}
}
}
package com.material.tdapps.hackernews.activity;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.material.tdapps.hackernews.JSON.JSONNewsParser;
import com.material.tdapps.hackernews.R;
import com.material.tdapps.hackernews.model.StoryFeed;
import com.material.tdapps.hackernews.model.StoryItem;
import java.net.MalformedURLException;
public class HomeFragment extends Fragment {
StoryFeed storyFeed;
Context context;
ListView listView;
NewsListAdapter newsListAdapter;
public HomeFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_home, container, false);
setRetainInstance(true);
context = this.getActivity();
storyFeed = new StoryFeed();
newsListAdapter = new NewsListAdapter(this);
listView = (ListView) rootView.findViewById(R.id.newsListView);
if (savedInstanceState != null && (savedInstanceState.getSerializable("previousFeed") != null)) {
Log.e("NOTIFY >> ", "GOING TO BUNDLE");
storyFeed = (StoryFeed)savedInstanceState.getSerializable("previousFeed");
listView.setAdapter(newsListAdapter);
newsListAdapter.notifyDataSetChanged();
} else {
Log.e("NOTIFY >> ", "GOING TO ASYNCTASK");
new AsyncLoadNewsFeed().execute();
listView.setAdapter(newsListAdapter);
}
setRetainInstance(true);
return rootView;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
@Override
public void onDetach() {
super.onDetach();
}
private class AsyncLoadNewsFeed extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
JSONNewsParser newsParser = new JSONNewsParser();
storyFeed = newsParser.parseJSON("https://hacker-news.firebaseio.com/v0/topstories.json",0,30);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if (storyFeed == null || storyFeed.getStoryCount() == 0){
StoryItem nullStoryItem = new StoryItem();
nullStoryItem.setTitle("ERROR: Null error!");
nullStoryItem.setNumberComments(0);
nullStoryItem.setScore(0);
nullStoryItem.setBodyText("NULL");
nullStoryItem.setTime(0);
nullStoryItem.setAuthor("NULL");
nullStoryItem.setUrl("http://www.google.com");
storyFeed.addStory(nullStoryItem);
}
newsListAdapter.notifyDataSetChanged();
}
}
class NewsListAdapter extends BaseAdapter {
private LayoutInflater layoutInflater;
public NewsListAdapter(HomeFragment homeFragment){
layoutInflater = (LayoutInflater) homeFragment.getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
return storyFeed.getStoryCount();
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
class listViewHolder {
TextView titleTxtV;
TextView timeTxtV;
TextView scoreTxtV;
TextView authorTxtV;
TextView domTxtV;
Button commentB;
RelativeLayout detailsLayout;
listViewHolder(View v) {
titleTxtV = (TextView) v.findViewById(R.id.titleText);
timeTxtV = (TextView) v.findViewById(R.id.timeText);
scoreTxtV = (TextView) v.findViewById(R.id.pointsText);
authorTxtV = (TextView) v.findViewById(R.id.authorText);
domTxtV = (TextView) v.findViewById(R.id.domainText);
commentB = (Button) v.findViewById(R.id.commentButton);
detailsLayout = (RelativeLayout) v.findViewById(R.id.newsDetailRelLayout);
}
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View listItem = convertView;
listViewHolder holder;
if (listItem == null) {
listItem = layoutInflater.inflate(R.layout.news_item_layout, parent, false );
holder = new listViewHolder(listItem);
listItem.setTag(holder);
} else {
holder = (listViewHolder) listItem.getTag();
}
holder.titleTxtV.setText(storyFeed.getStory(position).getTitle());
holder.timeTxtV.setText("Posted " + storyFeed.getStory(position).getTime());
holder.authorTxtV.setText(" By " + storyFeed.getStory(position).getAuthor());
holder.scoreTxtV.setText(storyFeed.getStory(position).getScore() + " Points ");
holder.commentB.setText(Integer.toString(storyFeed.getStory(position).getNumberComments()));
try {
holder.domTxtV.setText("(" + storyFeed.getStory(position).getURLDomain() + ")");
} catch (MalformedURLException e) {
e.printStackTrace();
}
holder.commentB.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent showComments = new Intent(getActivity(), CommentActivity.class);
getActivity().startActivity(showComments);
}
});
holder.detailsLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent showDetails = new Intent(getActivity(), DetailWebActivity.class);
Bundle detailsBundle = new Bundle();
//detailsBundle.putSerializable("newsFeed", storyFeed);
showDetails.putExtra("news",storyFeed);
showDetails.putExtras(detailsBundle);
showDetails.putExtra("position", position);
getActivity().startActivity(showDetails);
}
});
return listItem;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.material.tdapps.hackernews" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/HackerNewsTheme" >
<activity
android:name=".activity.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activity.CommentActivity"
android:label="@string/title_activity_comment"
android:parentActivityName=".activity.MainActivity"
android:configChanges="orientation|screenSize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.material.tdapps.hackernews.activity.MainActivity" />
</activity>
<activity
android:name=".activity.DetailWebActivity"
android:label="@string/title_activity_detail_web"
android:parentActivityName=".activity.MainActivity"
android:configChanges="orientation|screenSize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.material.tdapps.hackernews.activity.MainActivity" />
</activity>
</application>
</manifest>
Upvotes: 3
Views: 11091
Reputation: 15775
Calling setRetainInstance(true)
will prevent the Fragment
from being re-created. But, right now the Activity
is always re-creating the DrawerFragment
and forcefully re-creating the HomeFragment
, resulting in the behavior you are seeing. In your Activity.onCreate()
check the savedInstanceState
is null
. If it is, create your Fragment
s. If not, then don't as the system will automatically restore them.
Upvotes: 6
Reputation: 4573
You always create a new Fragment
inside your displayView
method. Instead, you should use FragmentManager
to find your fragment by Id or by Tag, and then put it inside your container view.
Please refer http://developer.android.com/reference/android/app/FragmentManager.html#findFragmentByTag(java.lang.String)
Edit
Actually @Larry Schiefer is right, you don't even need to find a fragment, just check the saved instance state whether it's null or not.
Upvotes: 0