Android FragmentTabHost questions - android

I looks like Android FragmentTabHost is poorly documented, so I am trying to understand basic things in this question.
1 Regarding code snippet: http://developer.android.com/reference/android/support/v4/app/FragmentTabHost.html
import com.example.android.supportv4.R;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTabHost;
/**
* This demonstrates how you can implement switching between the tabs of a
* TabHost through fragments, using FragmentTabHost.
*/
public class FragmentTabs extends FragmentActivity {
private FragmentTabHost mTabHost;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_tabs);
mTabHost = (FragmentTabHost)findViewById(android.R.id.tabhost);
mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
mTabHost.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
FragmentStackSupport.CountingFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
LoaderCursorSupport.CursorLoaderListFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"),
LoaderCustomSupport.AppListFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"),
LoaderThrottleSupport.ThrottledLoaderListFragment.class, null);
}
}
1.1 It shows the programmatic approach to adding tabs. Can I define avoid it? Can I just define everything in XML: number of tabs, their labels, their contents (links to fragment layouts also defined in XML)? If not why, because it looks like a hack to define application layout partially via static XML, programmatically (although the definition it is also static by its nature)?
1.2 Does this snippet imply any XML layout file? Is there an official example for it?
1.3 Supposing that this snippet DOES imply an XML layout file: what is realtabcontent and tabcontent? Should I have both? Why? It looks like some sort of hack again.
2 Here is an unofficial example of using FragmentTabHost http://maxalley.wordpress.com/2013/05/18/android-creating-a-tab-layout-with-fragmenttabhost-and-fragments/
2.1 Why LinearLayout should be put inside android.support.v4.app.FragmentTabHost? Is there an official documentation explaining that?
2.2 Why LinearLayout contains only one FrameLayout, not two like here Android FragmentTabHost - Not fully baked yet? ? Why we do not need #+id/realtabcontent and #android:id/tabs ?

Related

Android Fragments - where to save the state ( linked list with custom objects) ?

In my app, I have one activity and 9 fragments that are always replaced and kept on the back stack when the user goes from fragment 1 to fragment 2 the fragment 1's method onDestroyView is called and there i clean up all the fileds ( I have a big linkedlist with some custom object inside) and other objects that if i will keep them in memory, the OutOfMemory exception will be thrown.
Where should I store that list ? in a database ? in shared preferences ( here I saw that you need to make some hacks to make it possible).
At first I thought to store them in the bundle provided by the onSaveInstanceState method but the method is NEVER called, because it's tight to the Activity which acts just as a "container"container for all the fragments.
And I think relating on OnSaveInstanceState is not a good practice inside the Fragment (for what I need). So in on Pause I do all the fields clean up and I should store them some where and when the fragment is re created get back all the variables an make the view as the user left it. ( Like keeping the position inside a recycler view and so on..)
I searched a lot regarding this and didn't find any concrete answear regarding this.
So to make it a bit clear, the question is the following:
What is the best practice in android, to save the state of Fragments(the position from a recycler view + the list of objects that is displaying + other fields) ? Should I use a DB that will store all the fields or in SharedPreferences ? Or is there another way that I didn't mentioned/find ?
My main concern in all of this is what are the best practices in android regarding this subject, and performance.
After some more research, (in conclusion) I find that the best practice regarding this question is to use a database were you store all the attributes need for the recreation of the Fragment.
Regarding the data base, I read about REALM library on which I did some research, and I find that it's a very easy to use library and a very complex.
See more details here:
https://realm.io/docs/java/latest/
May be this can help. Android Developer ParceableYou can make the custom objects extend Parceable. Try lookin for Android Parceable example 2
you may use TabHost with fragments it will automatically maintain the states
This is my Main2Activity code
public class Main2Activity extends AppCompatActivity {
private FragmentTabHost mTabHost;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
mTabHost.setup(this, getSupportFragmentManager(), android.R.id.tabcontent);
mTabHost.addTab(
mTabHost.newTabSpec("tab1").setIndicator("Tab 1", null),
Tab1Fragment.class, null);
mTabHost.addTab(
mTabHost.newTabSpec("tab2").setIndicator("Tab 2", null),
Tab2Fragment.class, null);
mTabHost.addTab(
mTabHost.newTabSpec("tab3").setIndicator("Tab 3", null),
Tab3Fragment.class, null);
}}
here is my activity_main2.xml
<android.support.v4.app.FragmentTabHost
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<TabWidget
android:id="#android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"/>
</LinearLayout>
</android.support.v4.app.FragmentTabHost>

How to create a tab layout with a fixed number tabs, with static elements?

I'm trying to make an activity with a tab layout for my app. My aim is for this layout to have a fixed number of tabs (I'm thinking four, maybe five), each with static elements (labels, textviews, etc.). These elements will be filled with personal data corresponding to the user (like name, age, etc.) that will be taken from a database. My problem is that I can't find how to make this static layout.
I searched in the official documentation (http://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter.html), I tried the example in the link and it worked, but I can't figure out how to adapt it into what I'm trying to make.
Could someone give me a basic example of how to make this fixed tab layout?
In activity, call getTabHost() to get TabHost object. Individual tabs will be added to this object.
Call TabHost Object's newTabSpec() in order to create/inflate individual tabs.
After creating all the tabs by repeatedly performing step #2 above, finally, call addTab() method of TabHost object for the tabs be added and displayed.
private void createTabs() {
TabHost tabs = getTabHost();
getLayoutInflater().inflate(R.layout.main, tabs.getTabContentView(), true);
Resources resources = getResources();
TabHost.TabSpec tabOne = tabs.newTabSpec("TabOne")
.setContent(R.id.tabone)
.setIndicator(getString(R.string.tabone),
resources.getDrawable(R.drawable.tabone));
TabHost.TabSpec tabTwo = tabs.newTabSpec("TabTwo")
.setContent(R.id.tabtwo)
.setIndicator(getString(R.string.tabtwo),
resources.getDrawable(R.drawable.tabtwo));
TabHost.TabSpec tabThree = tabs.newTabSpec("TabThree")
.setContent(R.id.tabthree)
.setIndicator(getString(R.string.tabthree),
resources.getDrawable(R.drawable.tabthree));
tabs.addTab(tabon);
tabs.addTab(photo);
tabs.addTab(reservation);
}

changing the layout of current activity

FrameLayout content = (FrameLayout) findViewById(android.R.id.content);
This gives me an error
error: cannot find symbol
FrameLayout rootLayout = (FrameLayout)findViewById(android.R.id.content);
^
symbol: method findViewById(int)
I have already imported the required R package
It seems like you are trying to access the layout of your current Activity from a different class. Instead of trying to find your FrameLayout in the different class, save the reference to the FrameLayout inside of your Activity, and pass the FrameLayout to your seperate class (the class where you are currently seeing this issue).
E.g.
Activity Class:
...
OtherObject myOtherObject = new OtherObject();
FrameLayout frameLayout = (FrameLayout) findViewById(R.id.my_frame_layout);
myOtherObject.doStuffWithFrameLayout(frameLayout);
...
OtherObject Class:
...
public void doStuffWithFrameLayout(FrameLayout frameLayout) {
//You can use the FrameLayout here and do stuff with it.
//You will likely also want to pass in a Context object if you want to
//create a LayoutInflater or do other Context-dependent stuff
}
...
Try: FrameLayout content = (FrameLayout) findViewById(R.id.content);
In case this does not work remove the import of yourPackage.R and hit the button 'fix imports' not sure the import you did is correct. I always get 2 different options.
You can just call setContentView() again, but keep in mind that this will invalidate all of your View references, so make sure that you initialize them again.
There's almost never a reason to do this, so I would suggest you look into using Fragments, and just swap out the Fragments instead.
To use android.R.id.content, you must import android.R, not yourAppPackage.R .
And to use multiple layouts within one activity, you have to use ViewFlipper or ViewAnimator (or call setContentView multiple times, but it's resource expensive if you have huge layouts).

Android UI can be created by code *AND* XML?

I am having trouble grasping a certain concept in Android UI design. The book I am referring to first uses the usual technique that Java programmers use to create UIs and that is to to create containers and add UI components to them and nest them as necessary.
Now, the book introduces a new concept where the entire UI was created using an XML file. The code is pasted below:
package com.oreilly.android.intro;
import android.app.Activity;
import android.os.Bundle;
/**
* Android UI demo program
*/
public class AndroidDemo extends Activity {
private LinearLayout root;
#Override public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.main);
root = (LinearLayout) findViewById(R.id.root);
}
}
so basically, I can use any of them ?
Simple answer,yes, you can use either approach. However, there are some limitations to each such as there are layout properties that must be set in xml if you want to use them. I can't think of what any are off-hand but I can look them up.
For the most part, creating the layouts is much simpler to do in xml but you do have the option of setting Views and layouts in Java if you need to such as creating an unknown number of Buttons depending on some user-defined variable.
When you create your UI in xml then you inflate it in your Java code. This is normally done in onCreate() using
setContentView(R.layout.main);
as you see in your example. But it can also be done with an inflater.
The thing to remember here is to inflate your layout, using either method, before trying to initialize any views in the layout or you will get a NPE when trying to call a method on a View defined before inflating the layout it is contained in.
A correct way
**Examples of inflating views/layouts correctly**
Button mBtn;
public class AndroidDemo extends Activity {
private LinearLayout root;
#Override public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.main);
root = (LinearLayout) findViewById(R.id.root);
btn = (Button) findViewById(R.id.buttonId); // Button is initialized after inflating the layout
}
}
Incorrect way
public class AndroidDemo extends Activity {
private LinearLayout root;
#Override public void onCreate(Bundle state) {
super.onCreate(state);
Button mBtn = (Button) findViewById(R.id.buttonId); // Button is initialized before inflating layout which will return null
setContentView(R.layout.main);
root = (LinearLayout) findViewById(R.id.root);
}
}
I added the above example because I have seen a lot of people make that mistake. So don't do it...you've been warned! :)
Not entirely sure what you're asking, but the two are interchangeable.
Most of the time your UI will be done via xml. In some cases though, the ui is heavily dependent of the data, so you may need to dynamically generate it.
It basically comes down to whichever is easiest for you at the time.
Yes.
But is preferable to use xml, it is more powerful, easier and will separate layout from your code.
Take a look at the docs:
http://developer.android.com/guide/topics/ui/declaring-layout.html

getLoaderManager().initLoader() doesn't accept 'this' as argument though the class (ListFragment) implements LoaderManager.LoaderCallbacks<Cursor>

I'm having trouble following a guide on using SQLite in Android. I'm using a ListFragment instead of a ListActivity(as in the example), so I have the ListFragment implement LoaderManager.LoaderCallbacks<Cursor> instead. Then, in the fillData() method in the ListFragment:
private void fillData() {
// Fields from the database (projection)
// Must include the _id column for the adapter to work
String[] from = new String[] { NotesSQLiteHelper.COLUMN_TITLE };
// Fields on the UI to which we map
int[] to = new int[] { R.id.label };
getLoaderManager().initLoader(0, null, this); //error
adapter = new SimpleCursorAdapter(getApplicationContext(), R.layout.notes_row, null, from, to, 0);
setListAdapter(adapter);
}
I get the error:
The method initLoader(int, Bundle, LoaderManager.LoaderCallbacks<D>) in the type LoaderManager is not applicable for the arguments (int, null, NotesActivity.ArrayListFragment)
on the marked line even though this implements LoaderManager.LoaderCallbacks<Cursor>.
Thank you for any ideas.
You are not using the right implementations of CursorLoader and Loader.
Remove your old imports and use these ones:
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
But I have the same Problem using SherlockActionBar:
As I have to extend SherlockListActivity there is NO method getSupportLoadManager().
Any ideas on this?
EDIT: follow this tutorial if you do not know how to use fragments. Create a new Class with extends SherlockFragment and move your display logic there. Make your old activity extend SherlockFragmentActivity and show the newly created SherlockFragment. This way I got it working. Thanks to #JakeWharton!
A few things to watch out for (from my recent experience battling with this):
If your minSDK is set to less than 11 (i.e. level 10 for Gingerbread) and you are using the Support Pack for backward compatibility, make sure you use
getSupportLoaderManager().initLoader(LOADER_ID, null, this);
You mentioned this when you said you are using ListFragment, but it bears repeating: Do not extend Activity, otherwise the support package will not work. Instead, extend the FragmentActivity class or the ListFragment class.
For your imports, make sure you are using the correct versions if your minSDK < 11:
android.support.v4.app.FragmentActivity;
android.support.v4.app.LoaderManager;
android.support.v4.content.Loader;
Hope this helps you... or at least someone else...
Casting the third argument solved the problem in my case:
from
getLoaderManager().initLoader(0, null, this);
to
getLoaderManager().initLoader(0, null, (android.app.LoaderManager.LoaderCallbacks<Cursor>) this);
Note:
minSdk was 8 and i was using support library v4.
(android.support.v4.app.LoaderManager.LoaderCallbacks<Cursor>) this)
did not work.
getSupportLoaderManager() or getSupportLoadManager()
did not work.
This code was inside activity not fragment
It's late but maybe help some one.
if you use loader maneger in fragment and you min api is below HONEYCOMB
you shuld use this imports
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v4.app.Fragment;
and for initiate loader use this code
getActivity().getSupportLoaderManager().initLoader(/loader stuff*/);
hope this help some one.
Change your imports to
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
and use
getSupportLoaderManager().initLoader(0, null, this);
This is what you have:
getLoaderManager().initLoader(0, null, this);
This is what you should have:
LoaderManager.getInstance(this).initLoader(0, null, this);
Reason for the first one not working:
/**
* #deprecated Use
* {#link LoaderManager#getInstance(LifecycleOwner) LoaderManager.getInstance(this)}.
*/
My error was beacause this:
//import android.app.ListFragment; Error when doesnt import from support.v4
import android.support.v4.app.ListFragment;
In my case I had to have my Activity extend ActionBarActivity from the android.support.v7.app package. I was then able to use
getSupportLoaderManager().initLoader(0, null, this);
I'm using ActionBarSherlock with my app and I as well was running into this issue and worked through all the steps discussed by others in this question. However I was continuing to have the same problem after trying all the suggested resolutions.
The method initLoader(int, Bundle, LoaderManager.LoaderCallbacks<D>) in the type LoaderManager is not applicable for the arguments (int, null, this)
The issue was I had fallen into expecting Eclipse to tell me when I was missing something and it was telling me that but not in the way I was used to. Typically Eclipse in other cases would tell me I'm missing the overrides to make something work but it's not directly saying that here. I finally picked up on the "LoaderManager.LoaderCallbacks" being the issue and realized I had no callbacks for it thus this error was actually a very valid error. Adding the basic overrides resolved my issue and allowed me to move forward.
// Creates a new loader after the initLoader () call
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// do work
return null;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
adapter.swapCursor(data);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
// data is not available anymore, delete reference
adapter.swapCursor(null);
}
I was having a similar issue where my AsyncTaskLoader was not returning my List, but my resolution was to change the call from .initLoader to .restartLoader.
So I actually never called .initLoader and just immediately called .restartLoader.
I had a onListItemClick listener in my fragment and every time backed out of the fragment and reloaded the onListItemClick and navigated through the app it kept crashing.
It might just be specific to my app but hopefully it helps others if they are having fragment backstack reload issues.
This was part of a file manager portion in my app so it is specific to clicking multiple onListItemClicks in the fragment you are loading.
I got around this by using a SherlockListFragment (or you could use ListFragment, I suppose), but have it contained within a Activity. I first created a generic FragmentHolderActivity class that looks like this:
FragmentHolderActivity.java
public class FragmentHolderActivity extends SherlockFragmentActivity {
public static final String FRAGMENT_LAYOUT_RESOURCE_ID = "fragment_layout_resource_id";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Integer fragmentLayoutResourceId = getIntent().getIntExtra(FRAGMENT_LAYOUT_RESOURCE_ID, Integer.MAX_VALUE);
Assert.assertNotSame(fragmentLayoutResourceId, Integer.MAX_VALUE);
setContentView(fragmentLayoutResourceId);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return false;
default:
return super.onOptionsItemSelected(item);
}
}
}
Then you need to create an XML file for your fragment.
your_list_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="com.example.YourListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fragment" />
And here's the code you use to start the fragment:
Intent intent = new Intent();
intent.setClass(this, FragmentHolderActivity.class);
intent.putExtra(FragmentHolderActivity.FRAGMENT_LAYOUT_RESOURCE_ID, R.layout.your_list_fragment);
startActivity(intent);
You just tell the FragmentHolderActivity to use the your_list_fragment layout, which in turn loads the YourListFragment.java
You can then use: getSherlockActivity().getSupportLoaderManager().initLoader(...) in YourListFragment.java
Not sure if this is the correct approach, but it keeps all my logic in Fragments, which is nice.
Ran into the same problem. My minSdkVersion is 14, so cannot use android.support.v4 package.
I figured it by extending LoaderCallbacks, instead of LoaderManager.LoaderCallbacks and use these packages
import android.app.LoaderManager.LoaderCallbacks;
import android.content.CursorLoader;
import android.database.Cursor;
import android.widget.SimpleCursorAdapter;
If you have tried all the above methods and still facing the same error with the "this" parameter, then follow these steps :
Go to settings and enable imports on the fly option.(by default it'll
be enabled and you can do this by using Alt+Enter keys).
Cut the whole code of the activity which had implemented
LoaderCallback... and paste it in a text editor.
Then at last copy the whole code of that activity from the text editor
where you had pasted, without any import commands.( Only the code of
the class/activity).
You'll get lots of errors as you have imported nothing yet.
Just press Alt+Enter wherever you're getting the errors and it's
libraries will be imported automatically.
Note : Choose android.app... library for CursorLoaders.
Try these 2 lines it will work
android.support.v4.app.LoaderManager loaderManager = getSupportLoaderManager();
loaderManager.initLoader(LOADER_ID, null, this);
I am using minSDK 27 and had this same issue, I tried to cast like #Dexter suggest, but that gave an error saying cannot be cast to android.app.LoaderManager$LoaderCallbacks, so I then tried to use different import statements and this worked. I commented out the v4 versions and this is what I have now and the app is working:
//import android.support.v4.app.LoaderManager;
import android.app.LoaderManager;
import android.support.v4.app.NavUtils;
//import android.support.v4.content.CursorLoader;
import android.content.CursorLoader;
//import android.support.v4.content.Loader;
//import android.content.Loader;
Not quite sure when/how the v4 versions were imported.
0
After a long sacrifice i got this solution if you are using fragments then just use this code.
getActivity().getSupportLoaderManager().initLoader(1, null, YourActivity.this);
i hope this is helpful for you
I was trying to call initLoader() inside an activity. For me this worked
changing
getSupportLoaderManager().initLoader(LOADER_ID,null,this);
to
getLoaderManager().initLoader(LOADER_ID,null,this);
If you use API 28 or later, instead of getLoaderManager use:
getSupportLoaderManager().initLoader(id, null, this);
If you use API 29, instead of getLoaderManager use:
getSupportLoaderManager().initLoader(id, null, this);

Categories

Resources