FragmentActivity crashing on orientation change (MainActivity seen as Fragment) - android

I am using a SherlockFragmentActivity with tabs. It loads fine the first time, but when the orientation gets changed, the following error occurs:
java.lang.RuntimeException: Unable to start activity
ComponentInfo{ext.domain.app/ext.domain.app.MainActivity}:
android.support.v4.app.Fragment$InstantiationException: Unable to instantiate
fragment ext.domain.app.MainActivity$1: make sure class name exists, is public,
and has an empty constructor that is public
I've been looking at posts about similar problems, but there is a difference: in this error it seems like MainActivity is seen as a Fragment ("unable to instantiate fragment ext.domain.MainActivity"), instead of the used Fragment class being mentioned.
It does not help to give MainActivity an empty constructor. The Fragments that I actually use are called ArticleListFragment and it is a public class with an empty constructor.
I'm running the app on Android 2.3, below is the code (stripped of most irrelevant details and still throwing the exception):
MainActivity.java:
(Edit: added the instantiation of the ArticleListFragment which is an anonymous inner type. It seems that if I remove it, it does work.)
public class MainActivity extends SherlockFragmentActivity implements ActionBar.TabListener {
ActionBar actionBar;
public MainActivity() {
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
// Create ActionBar
actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Create Tabs
String[] tabs = {
getString(R.string.tab1),
getString(R.string.tab2),
getString(R.string.tab3),
getString(R.string.tab4)
};
for(String tabname : tabs) {
ActionBar.Tab tab = actionBar.newTab();
tab.setText(tabname);
tab.setTabListener(this);
actionBar.addTab(tab);
}
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
switch(tab.getPosition()) {
case 0:
ArticleListFragment home = new ArticleListFragment() {
public void onAttach(Activity activity) {
// This is some code to populate the Fragment with an HTTPRequest
super.onAttach(activity);
RequestParams paramsHome = RequestClient.getBasicRequestParams(activity);
populate("frontpage", paramsHome);
};
};
ft.replace(R.id.contentframe, home);
break;
case 3:
ArticleListFragment saved = new ArticleListFragment() {
public void onAttach(Activity activity) {
// This is some code to populate the Fragment with an HTTPRequest
super.onAttach(activity);
RequestParams paramsSaved = RequestClient.getBasicRequestParams(activity);
populate("saved", paramsSaved);
};
};
ft.replace(R.id.contentframe, saved);
break;
}
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
ArticleListFragment.java:
public class ArticleListFragment extends SherlockFragment {
public ArticleListFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(final LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.fragment_articlelist, null);
return layout;
}
}
Any ideas why this code is wrong?

What's the full code for MainActivity? It isn't complaining about trying to instantiate MainActivity, but an anonymous inner class (indicated by the $1) of MainActivity.

Related

FragmentTransaction within Fragment

im a new guy on this programming world so i guess this question is simple. So I have one imagebutton on my fragment and I whatI want is that when I click on it, it does a fragment transaction to another fragment; but the thing is that when I run the app and click on the imagebutton, it only "superimposes" the content of the other fragment into the one where the imagebutton is, of course what i want to do is just to go to the other fragment, ive been dealing with this for a while so I hope someone helps me, thanks.
Here is my java code of the fragment
public class inicio extends Fragment {
public inicio() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.fragment_inicio,container,false);
ImageButton luces = (ImageButton)view.findViewById(R.id.imageButton);
luces.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
interior interior= new interior();
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.inicioo, interior).commit();
}
});
}
}
New code added...
public class transaction extends MainActivity
implements com.example.administradora.prueba.inicio.OnSelectedListener{
public void onButtonSelected() {
interior interior= new interior();
getSupportFragmentManager().beginTransaction().replace(R.id.inicioo, interior).commit();
}
}
but i get this error in the logcat:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.administradora.prueba/com.example.administradora.prueba.MainActivity}: java.lang.ClassCastException: com.example.administradora.prueba.MainActivity#20f8fe9b must implement OnSelectedListener
You shouldn't replace a Fragment from another Fragment. Try to do that through the Activity.
Define some interface for the Fragment to hold
public class MyFragment extends Fragment {
OnSelectedListener mCallback;
// Container Activity must implement this interface
public interface OnSelectedListener {
public void onButtonSelected();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnSelectedListener ) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnSelectedListener");
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.fragment_inicio,container,false);
ImageButton luces = (ImageButton)view.findViewById(R.id.imageButton);
luces.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Send the event to the host activity
if (mCallback != null) mCallback.onButtonSelected();
}
});
return view;
}
}
And swap the Fragment container in the interface implementation from the Activity.
public class MainActivity extends Activity
implements MyFragment.OnSelectedListener{
...
public void onButtonSelected() {
interior interior= new interior();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.inicioo, interior)
.commit();
}
}
The first two sections of Communicating with Other Fragments is what you are looking for.

Android : Fragment and Observer

Main goal is to update Fragment info mainly from its own class.
Main activity:
public class MainActivity extends AppCompatActivity {
final Handler GUIHandler = new Handler();
final Runnable r = new Runnable()
{
public void run()
{
updateFragments();
GUIHandler.postDelayed(this, 1000);
}
};
#Override
protected void onPause() {
super.onPause();
GUIHandler.removeCallbacks(r);
}
#Override
protected void onResume() {
super.onResume();
GUIHandler.postDelayed(r, 600);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
...
mViewPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new PagerAdapter(getSupportFragmentManager(), tabLayout.getTabCount());
mViewPager.setAdapter(mPagerAdapter);
...
}
private void updateFragments() {
mPagerAdapter.updateFragments();
}
PagerAdapter:
public class PagerAdapter extends FragmentStatePagerAdapter {
int mNumOfTabs;
private Observable mObservers = new FragmentObserver();
public PagerAdapter(FragmentManager fm, int NumOfTabs) {
super(fm);
this.mNumOfTabs = NumOfTabs;
}
#Override
public Fragment getItem(int position) {
mObservers.deleteObservers(); // Clear existing observers.
switch (position) {
case 0:
FragmentWeather weatherTab = new FragmentWeather();
weatherTab.setActivity(mActivity);
if(weatherTab instanceof Observer)
mObservers.addObserver((Observer) weatherTab);
return weatherTab;
case 1:
FragmentMemo tab2 = new FragmentMemo();
return tab2;
case 2:
FragmentHardware tab3 = new FragmentHardware();
return tab3;
default:
return null;
}
}
public void updateFragments() {
mObservers.notifyObservers();
}
}
FragmentObserver
public class FragmentObserver extends Observable {
#Override
public void notifyObservers() {
setChanged(); // Set the changed flag to true, otherwise observers won't be notified.
super.notifyObservers();
Log.d("Observer", "Sending notification");
}
}
FragmentWeather:
public class FragmentWeather extends Fragment implements Observer {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
return layout;
}
public void setTemperatures(){
Log.d("Android", "setTemperatures is called");
}
#Override
public void update(Observable observable, Object data) {
setTemperatures();
}
}
Problem now is, that PagerAdapter::getItem() doesnt get called when Fragments are created at the start of application. That means WeatherFragment dont get associated with mObservers. If I swipe to the 3rd view and then swipe back, everything is working properly. How to restructurize this to make it working?
this line:
mObservers.deleteObservers(); // Clear existing observers.
is removing all the observers, but the method getItem gets called several times, that means only the last time it calls anything stays there. REMOVE this line.
Also, the following code is a very bad pattern and it will go wrong on several occasions:
case 0:
FragmentWeather weatherTab = new FragmentWeather();
weatherTab.setActivity(mActivity);
if(weatherTab instanceof Observer)
mObservers.addObserver((Observer) weatherTab);
return weatherTab;
that's because fragments get re-created by the system when necessary, so setActivity is pointless, so as is addObserver. The moment the system needs to destroy/recreate the fragments, you'll have a memory leak of those old fragments, the old activity, and the new ones won't have the activity and won't be on the observers.
The best situation here is to rely on the natural callbacks from the fragments. An example follows (ps.: that was typed by heart, I'm sure there might be some mistakes, but you'll get the idea)
public interface ObservableGetter{
public Observable getObservable();
}
public void MyFragment extends Fragment implements Observer {
#Override onAttach(Activity activity){
super.onAtttach(activity);
if(activity instanceof ObservableGetter){
((ObservableGetter)activity).getObservable().
addObserver(this);
}
}
#Overrude onDetach(){
Activity activity = getActivity();
if(activity instanceof ObservableGetter){
((ObservableGetter)activity).getObservable().
removeObserver(this);
}
super.onDetach();
}
}
then you can just make the activity implements ObservableGetter and have the Observable on it.
Then your adapter code will be just:
case 0:
return new FragmentWeather();
all the rest of the logic uses the regular callbacks.
I hope it helps.

I get a NullPointerException when i try to modify the height of a layout in onTabSelected()

Here is my quite simple problem:
I get a NullPointerException when i try to modify the height of a layout in onTabSelected().
Here is the code:
import...
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
tab1.setTabListener(new MyTabListener(fragmentTabCustom));
actionBar.addTab(tab1);
}
...
public void changeFragmentTabCustom() {
runOnUiThread(new Runnable() {
#Override
public void run() {
layout1 = (LinearLayout) findViewById(R.id.layoutdefault);
layout1.getLayoutParams().height = 0; // the exception appends on this line
layout1.setLayoutParams(layout1.getLayoutParams());
}
}
}
...
}
public class MyTabListener implements ActionBar.TabListener {
...
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
mainActivity.changeFragmentTabCustom();
...
}
}
What is wrong?
This happens because your view's layoutparams are set by the Parent(Viewgroup).
Wait till the view is attached to the window (add onlayout listeners) or you can create new LayoutParams with hight as "0".

How to call notifydatasetchanged when using SherlockFragmentActivity and ListFragments

I have a Sherlock Tab Navigation implemented as the following two classes. Class Dashboard is the main activity. Class DashboardContacts represents one of the tab's fragment.
I am loading HTTP data that gets loaded in a while and when it loads I need the list view in DashboardContacts to reflect the changes brought in from Server and refresh the ListView from blank to filled list. For this i'm calling notifyDataSetChanged method through refresh method in DashboardContacts, but no change relects in ListView until I change the tabs.
public class Dashboard extends SherlockFragmentActivity {
private DashboardContacts contactsTab=new DashboardContacts();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dashboard);
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
ActionBar.Tab tab = getSupportActionBar().newTab();
tab.setText(getResources().getStringArray(R.array.dashboardTabs)[0]);
tab.setTabListener(new DashboardHome());
getSupportActionBar().addTab(tab);
ActionBar.Tab tab2 = getSupportActionBar().newTab();
tab2.setText(getResources().getStringArray(R.array.dashboardTabs)[1]);
tab2.setTabListener(contactsTab);
getSupportActionBar().addTab(tab2);
//CALLS ASYNCLOADER HERE TO LOAD HTTP DATA
}
private void httpSuccessMethod() {
//Does some work and then calls:
contactsTab.refresh(datasource.getAllContacts());
}
Here is the DashboardContacts class layout:
public class DashboardContacts extends SherlockListFragment implements ActionBar.TabListener{
private UserListAdapter listAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
listAdapter=new UserListAdapter(container.getContext(), ApplicationState.getInstance(container.getContext()).getUserCache());
setListAdapter(listAdapter);
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void onStart() {
super.onStart();
}
#Override
public void onTabSelected(Tab tab,android.support.v4.app.FragmentTransaction ft) {
ft.replace(android.R.id.content, this,"contacts");
ft.attach(this);
}
#Override
public void onTabUnselected(Tab tab,android.support.v4.app.FragmentTransaction ft) {
ft.detach(this);
}
#Override
public void onTabReselected(Tab tab,android.support.v4.app.FragmentTransaction ft) {
// TODO Auto-generated method stub
}
public void refresh(List<User> list) {
if(this.listAdapter != null) {
this.listAdapter.setList(list);
this.listAdapter.notifyDataSetChanged();
}
}
The refresh method in the custom list adapter looks like, so I am actually changing the data there too:
public void refresh(List<User> list) {
if(this.listAdapter != null) {
this.listAdapter.setList(list);
this.listAdapter.notifyDataSetChanged();
}
I think this is something I didn't implement, so I did it now and solved the issue:
public void setList(List<ExpensePool> list) {
clear();
addAll(list);
notifyDataSetChanged();
}

ActionBar and ViewPager and CursorLoader works, but maybe there is better way

My application loads data from database (ContentResolver) and make tabs in ActionBar with ViewPager. The following code works as expected but I want someone to review my code. I need to know if it complies with the general patterns and practices? Does it need a change? Can I get some opinions?
public class MainActivity extends SherlockFragmentActivity
{
private static final String TAG = MainActivity.class.getSimpleName();
TabAdapter tabAdapter;
ViewPager viewPager;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tabAdapter = new TabAdapter(getSupportFragmentManager());
viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPager.setAdapter(tabAdapter);
final ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener()
{
#Override
public void onPageSelected(int position)
{
actionBar.setSelectedNavigationItem(position);
}
});
}
private class TabAdapter extends FragmentPagerAdapter implements ActionBar.TabListener, LoaderManager.LoaderCallbacks<Cursor>
{
ArrayList<String> lists = new ArrayList<String>();
ArrayList<Fragment> fragments = new ArrayList<Fragment>();
public TabAdapter(FragmentManager fragmentManager)
{
super(fragmentManager);
getSupportLoaderManager().initLoader(0, null, this);
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft)
{
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft)
{
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft)
{
}
#Override
public Fragment getItem(int arg0)
{
return fragments.get(arg0);
}
#Override
public int getCount()
{
return fragments.size();
}
#Override
public CharSequence getPageTitle(int position)
{
return lists.get(position);
}
#Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1)
{
CursorLoader cursorLoader = new CursorLoader(
MainActivity.this,
TasksContentProvider.TaskLists.CONTENT_URI,
null,
null,
null,
null);
return cursorLoader;
}
#Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor c)
{
while (c.moveToNext())
{
String title = c.getString(c.getColumnIndex(TasksContentProvider.TaskLists.Columns.TITLE));
lists.add(title);
TaskListFragment fragment = new TaskListFragment();
Bundle bundle = new Bundle();
bundle.putInt("LIST_ID", c.getInt(c.getColumnIndex(TasksContentProvider.TaskLists.Columns._ID)));
fragment.setArguments(bundle);
fragments.add(fragment);
final ActionBar actionBar = getSupportActionBar();
actionBar.removeAllTabs();
for (int i = 0; i < getCount(); i++)
{
actionBar.addTab(actionBar.newTab().setText(getPageTitle(i)).setTabListener(this));
}
}
}
#Override
public void onLoaderReset(Loader<Cursor> arg0)
{
}
}
}
That completely depends on your design. If you are willing to use SherlockActionBar then you're likely to be dependent on Some one else's design/code. However you can find many exmaples and tutorials of how to make use of SherlockActionbar. However you'll find that there are many new modules that can be used to create similar functionality as of ActionbarSherlock. Please take a look at the following link to find new features in v4 and v7 libraries,
http://developer.android.com/tools/support-library/index.html
Few suggestions on code structuring . Try to modularize your code even though as of now its readable. I hope this helps.
i'm using mFragmentStatePageAdapter.notifyDataSetChanged(); in onLoadFinished(Loader<Cursor> loader, Cursor data) to notify changes of data, and if you move the cursor loader part to MainActivity, it also works. personally i prefer not to implements cursor loader in adapter.

Categories

Resources