Android: TabHostFactory.createTabContent not being called for all tabs? - android

I have an app that needs tabs with one list in the content of each tab. Those lists need to refresh several times without changing the tab. The lists represent trees of possible choices, so the user clicks element 4, for example, and I have to refresh the list to show the elements under 4.
The easiest (and probably ugliest) way I found to accomplish this is to attach a listener to the list "currently" shown, and launch "this" activity again (with some extras relating the element the user clicked), effectually recreating the whole view, tabs and everything. This allows me to keep each state of the lists independent, and makes the back support easy enough. --I said it was ugly!
For that to work, I create the tabs inside onCreate, like this:
TabHost.TabSpec spec1 = tabHost.newTabSpec("TOC").setIndicator("TOC", res.getDrawable(R.drawable.ico_table_of_contents)).setContent(this);
tabHost.addTab(spec1);
TabHost.TabSpec spec2 = tabHost.newTabSpec("BLAH").setIndicator("BLAH", res.getDrawable(R.drawable.blah)).setContent(this);
tabHost.addTab(spec2);
The .setContent(this) works because this activity extends TabActivity and implements TabHost.TabContentFactory. The createTabContent function returns this:
return from(this).inflate(R.layout.new_list, getTabHost().getTabContentView(), true);
no matter which tab I'm in.
After this, I update the List with an adapter, like this:
private void updateList(ArrayList<Element> items) {
ElementAdapter adapter = new ElementAdapter(this, R.layout.element, items);
ListView list = (ListView) findViewById(R.id.theListView);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {...}
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
The problem is that the first tab works great, but the second doesn't. It needs just the same view as the first one (the list showing a list of elements), but it shows nothing-zippo-nada, just the tabs. The ArrayList I pass in has elements in both cases.
Walking the code, I noticed that in my createTabs, the createTabContent is called ONCE, on tabHost.addTab(spec1) (for the first tab), but NOT when adding the second.
What gives? Why do I get an empty screen instead of the list, if both cases have elements to show?
I hope this makes sense. It's a bizarre problem to explain and I'm not sure if I'm making much sense.
Any help will be appreciated.
Thanks
llappall

I have never tried using the same TabContentFactory more than once. I would have expected what you used here to work, particularly given the tag parameter to createTabContent().
That being said, try using multiple TabContentFactory objects and see if that helps (e.g., use instances of anonymous inner classes).

Related

What's the correct way of displaying ViewPager after associated ListView's item click?

I'm a beginner in Android, so I apologize for the mistakes and I'd appreciate any constructive criticism.
I'm writing a basic application with a ListView of images, and when the user clicks on an item in the list, I want to display that image in a ViewPager, where the user can swipe back and forth to browse the whole list of images. Afterwards when the user presses the back button, I want to switch back to the ListView.
I manage the business logic in the MainActivity, which uses MainActivityFragment for the ListView and ImageHolderFragment for ViewPager.
The simplified code so far is as follows:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListItems = new ArrayList<>();
mListItemAdapter = new ListItemAdapter(this, R.layout.list_item, R.id.list_item_name, mListItems);
mListView = (ListView) findViewById(R.id.list_view_content);
mListView.setAdapter(mListItemAdapter);
mDeletedListItems = new ArrayList<>();
mViewPager = (ViewPager) getLayoutInflater().inflate(R.layout.image_display, null, true);
mImageAdapter = new ImageAdapter(getSupportFragmentManager(), mListItems);
mViewPager.setAdapter(mImageAdapter);
mViewPager.setOffscreenPageLimit(3);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mViewPager.setCurrentItem(position);
setContentView(mViewPager); // TODO: this is very wrong!
}
});
loadImages();
noContentText = (TextView) findViewById(R.id.no_content_text);
if (mListItems.isEmpty()) {
noContentText.setText(R.string.no_images);
} else {
mImageAdapter.notifyDataSetChanged();
}
}
Although this does work to some extent, meaning that it manages to display the ViewPager when an item in the list is clicked, there are two things about it ringing the alarm bells:
I've read that calling setContentView() for the second time in the same class is pretty much a sin. Nobody explained me why.
The back button doesn't work in this case. When it's pressed, the application is terminated instead of going back to the list view. I believe this is connected to the first point.
I would appreciate any help, explanations if my idea is completely wrong, and if my case is hopeless, I'd like to see a successful combination of ListView and ViewPager with transitions between each other.
Your activity already has R.layout.activity_main set as content view, which rightly displays the list view - that's what the responsibility of this activity is as you defined it. If we want to change what's shown on the screen, we should use a different instance of a building block (activity or fragment) to display the view pager images.
To say the least, imagine if you wanted to change the view to a third piece of functionality or UI, or a fourth... it would be a nightmare to maintain, extend and test as you're not separating functionality into manageable units. Fields that are needed in one view are mixed with those needed in another, your class file would grow larger and larger as each view brings its click listeners, callbacks, etc., you'd also have to override the back button so it does what you want - it's just not how the Android framework was designed to help you. And what if you wanted to re-use UI components in different contexts whilst tapping in to the framework's activity lifecycle callbacks? That's why fragments were introduced.
In your case, the list view could continue to run in your MainActivity and in your click listener, onItemClick you could start a new activity that will hold a viewPager:
Intent i = new Intent(MainActivity.this, MyLargePhotoActivityPager.class);
i.putExtra(KEY_POSITION, position);
// pass the data too
startActivityForResult(i, REQUEST_CODE);
Notice how you could pass the position to this activity as an int extra, in order for that second activity to nicely set the viewPager to the position that the user clicked on. I'll let you discover how to build the second activity and put the ViewPager there. You also get back button functionality assuming your launch modes are set accordingly, if needed. One thing to note is that when you do come back to the list View, you'd probably want to scroll to the position from the view pager, which is why you could supply that back as a result via a request code. The returned position can be supplied back to the list view.
Alternatively, you could use the same activity but have two fragments (see the link further above) and have an equivalent outcome. In fact, one of your fragments could store the list view, and the second fragment could be a fullscreen DialogFragment that stores a viewPager, like a photo gallery (some details here).
Hope this helps.
I've read that calling setContentView() for the second time in the
same class is pretty much a sin. Nobody explained me why.
Well, you kind of get an idea as to why.
When you use setContentView() to display another 'screen' you do no have a proper back stack.
You also keep references to Views (like mListView) that are not visible anymore and are therefore kind of 'useless' after you setContentView() for the second time.
Also keep in mind orientation changes or your app going to the background - you'll have to keep track of the state that your Activity was in which is way more complicated than it has to be if you have one Activity that does two different things.
You won't be arrested for doing things like you do right now, but it's just harder to debug and keep bug free.
I'd suggest using two different Activities for the two different things that you want to do, or use one Activity and two Fragments, swapping them back and forth.
If you insist on having it all in one Activity you need to override onBackPressed() (called when the user presses the back button) and restore the first state of your Activity (setContentView() again, pretty much starting all over).

Open several layouts from a single activity (listview)

I created a list of drugs in my main activity using a ListView in Android Studio.
Since drugs are many and have same characteristics (indication, contraindication, posology) I'd like to know if there is an alternative to create an activity for each drug. In other words: is it possible to open different layouts from one single activity? Is it possible to create a "template" layout and insert only the contents (posology and so on...)? I tried with Fragment but I failed miserably. Alternatively, creating a lot of activity can slow down my application? Thank you (I apologyze for my terrible english grammar)
What I would suggest doing is creating something like a DrugDetail activity which you pass information about the drug to. This means instead of creating loads of activities for every drug you just create one activity and pass it all the drugs information.
So what you would do is set an onItemClickListener onto your drug listview the. Inside that listener method have something like this
Intent intent = new Intent(MyActivity.this, DrugDetail.class);
intent.putExtra("drugName", "drugnamestring");
intent.putExtra("drugDescription", "drugDescriptionString");
startActivity(intent);
In your case the best practice is creating base layout (extends common layouts e.g. FrameLayout or RelativeLayout) witch you will inflate in your own class witch will extends ListView class for all items of this custom ListView. When user clicks on list's item it extends and shows him all information about particular drug. This video is all you need, enjoy! http://www.youtube.com/watch?v=mwE61B56pVQ

Android: Dynamically Change Activity Title to Text of Selected List Item

I'm new to Android development.
I created a simple master-detail app that starts with a simple, vertical scrolling list of topics.
When the user selects a topic, a details screen appears, replacing the first screen, with a list of details that pertain to the selected topic.
I want the title for the details screen to show the topic the user has selected on the first page, but haven't been able to solve the problem after working for almost a week.
All I need to know is, Can this be done? Not looking for someone to solve this for me, but maybe a hint or a link to a tutorial that shows how this can be done.
Note: I'd post a drawing of what I want to do, but I'm new here and don't have 10 reputation yet.
Thanks,
SonCoder
Not exactly sure what you want but either way..
-You have a listview. Each view (the data) in the listview should be a represented by a model. (aka a separate class containing specific information that you want to represent for each listitem.
-Write a custom list adapter (extend from base adapter).
http://www.androidhive.info/2012/02/android-custom-listview-with-image-and-text/
In the getView method of this class you load the the String field of the model that you want in the textview.
-Make sure to use the viewholder pattern in the adapter above. I noticed the example doesnt use one. This speeds up scrolling in the list because there are much fewer calls to findViewById.
-in the list activity set up a View onClick listener. This should create an intent (for launching an activity) or a fragment transaction (for fragments). Send the instance of your entire model (will get from
parent.getAdapter().getItem(position);
in the on click method) into the detail activity.
-if you want to set a textview title just get the textview and set it from the model. It will be the same filed you inflated in the getView method of the adapter.
-if you want to set the titile in the actionbar set:
this.getActionBar().setTitle(title)
This is simple. Just send extra data in the intent that starts the activity and then in the activity's onCreate read the data and then use the setTitle(myString) method from the activity.
setTitle(String title) can be called from anywhere using the activity by the way.
So, your in your listadapter, then you set a listener on your view right? A simple onClickListener on the whole "root" view is just fine.
In the listener you say something in the ways of this:
Intent intent = new Intent(myActivity, MySubActivity.class);
intent.putExtra(key, titleName);
myActivity.startActivity(intent);
Note that the activity reference should be set in the constructor of the adapter and that the "key" String is something you get from your strings.xml. Do not duplicate these in code since if you change one and forget to change the others you might get some wierd NPEs.
Continue in your MySubActivity's onCreate()
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
String key = getString(R.string.my_title_key);
String title = intent.getString(key);
setTitle(title);
}
NOTE: I'm not sure of all method names are correct and such but something like this.

Removing a tab and the activity (intent) inside of it from a TabHost

I have an app that can create tabs dynamically. And when I create a tab I initiate an activity as an intent. Like so:
private void addTab(Context packageContext, Class<?> newClass, TabHost mTabHost, String tabId, String tabLabel){
// newClass is my Activity class that I want to start in the tab
Intent intent = new Intent().setClass(packageContext, newClass);
TabHost.TabSpec spec;
spec = mTabHost.newTabSpec(tabId).setIndicator(tabLabel)
.setContent(intent);
mTabHost.addTab(spec);
mTabHost.setCurrentTabByTag(tabId);
}
Pretty standard. And it works great. Now, suppose that I have a button (or menuitem, whatever) in the activity that I instantiated inside of my tab. When the user presses this button, I want the activity, and the tab it is inside of, to be removed and destroyed.
I can't seem to find a simple way to do this. I have found the TabHost.clearAllTabs() function, but this destroys all tabs and activities, I just want to remove one.
Someone suggested I save a list of all Tabs that I have opened, and then call clearAllTabs(), after which I recreate all of my other tabs except for the one I don't want.
Something like this:
public static ArrayList<TabHost.TabSpec> list = new ArrayList<TabHost.TabSpec>();
I add this line to my addTab() function so that every tab I create is remember in my ArrayList:
list.add(spec);
And then when I want to remove my tab I run this function:
public static void removeTab(){
list.remove(list.size()-1); // remove it from memory
mTabHost.clearAllTabs(); // clear all tabs from the tabhost
for(TabHost.TabSpec spec : list) // add all that you remember back
mTabHost.addTab(spec);
}
This removes my tab from my ArrayList, removes all tabs, then recreates all the tabs remaining using my ArrayList. In theory it should work, but I get the following error when I try call this function:
FATAL EXCEPTION: main
java.lang.NullPointerException
at android.widget.TabWidget.setCurrentTab(TabWidget.java:342)
at android.widget.TabWidget.focusCurrentTab(TabWidget.java:366)
at android.widget.TabHost.setCurrentTab(TabHost.java:323)
at android.widget.TabHost.addTab(TabHost.java:216)
at com.example.myapp.TabManager.removeTab(QuikBrowser.java:86)
at com.example.myapp.TabManager.TabWindow.onOptionsItemSelected(TabWindow.java:91)
at android.app.Activity.onMenuItemSelected(Activity.java:2205)
For some reason, when adding a tab, it attempts to set the current tab, and it hits a null pointer exception.
If you guys could suggest another way of achieving what I want to do, or a way to fix my current method, I would appreciate it.
Try changing current tab to 0.
Something like:
getTabHost().setCurrentTab(0);
getTabHost().clearAllTabs();
I was reading that calling clearAllTabs(); will throw a nullpointerexception if you don't set the tabhost to the first tab (.setCurrentTab(0)) before calling (.clearAllTabs())
Also this answer may help? (How to remove tab from TabHost)
I would suggest a different approach. You can use an ActivityGroup to build your own TabControl. As you are using normal Buttons (or similar controls just as you like) you can easyly arrange/create/remove them as needed.
I can't dump the whole code here but that is basically what I did when I had the same problem:
Create an Activity inherited from ActivityGroup
Place a ViewGroup in your layout where you want to show the sub-activities
Setup your buttons as needed (LinearLayout works fine with a variable count of buttons)
Start activites thru getLocalActivityManager().startActivity() as needed
You can now add/remove buttons as you like. The Activites follow the Android lifecycle so you don't have to delete them yourself.
You might have to implement onBackPressed on your ActivityGroup to properly handle the history but that depends on the project.

slight misunderstanding with tabActivity, views and activity

am not too experienced in android and i am just using the TabActivity, so please bear with me. i keep seeing post about not using activites in Tabhost but views. and am not sure which is which and if thats the reason of my latest headache? i have a code with 4 tabs like this:
// Category TabActivity class
tab.setContent(new Intent(this, Mylist.class));
tab1.setContent(new Intent(this, Mylist.class));
tab2.setContent(new Intent(this, Mylist.class));
and each tab is showing the result of a method in the Mylist Activity.
is that using an activity in a tab or displaying a view? whats the difference?.
i have a context menu on an item selected in the Mylist Activity which updates the list. how can i reflect the changes in the list back to the tab in other to display that particular method in the Mylist class, that was set as the tabs content. obviously this will change if i updated or deleted an item from the list when the tab is shown. i think its possible with onResume(), but don't know what to call there or is there any better way?
in the Mylist class, i have tried this little piece of code to restart the TabActivity:
myAdapter.deleteItem(id);
fillData(); //
Intent refereshCategory = new Intent(this, Category.class);
startActivity(refereshCategory);
its restarting the activity after the item have been deleted, but how can i only show the tab whose view was in focus when it restarts and i don't want fillData() method to be shown as it does not have to do with the tabs. i hope i made myself clear enough. Thanks
Any help will be greatly appreciated. Thanks for your time.
With tab.setContent(new Intent(this, Mylist.class)); You are telling the application to display a new MyList Activity in that tab.
I'm not sure of the activity lifecycle for tabbed activities but I think it is safe to assume they follow the same standards as regular activities. onResume is called every time the activity shown (which would include being switched from another tab's activity to this activity). So any kind of updating you want to do to the list every time it is shown should go here and not in onCreate(Bundle savedInstanceState)
To create any kind of Android application you should become familiar with the Component Lifecycle

Categories

Resources