i am launching a activity hosting a fragment with a list of items..
When an item is clicked. The layout is tested for threePane or not.
public void onFeedSelected(Feed feed) {
if (isThreePane) {
addItemsFragment(feed);
}
else {
Intent i=new Intent(this, ItemsActivity.class);
i.putExtra(ItemsActivity.EXTRA_FEED_KEY, feed.getKey());
startActivity(i);
}
}
As you can see if it is not three pane then it launches an activity..This works fine on regular phones.
But if it is a tablet screen the activity isnt launched in the fragment.
Here is my AddFeed() method.
public void addItemsFragment(Feed feed) {
FragmentManager fragMgr=getSupportFragmentManager();
ItemsFragment items=(ItemsFragment)fragMgr.findFragmentById(R.id.second_pane);
FragmentTransaction xaction=fragMgr.beginTransaction();
if (items==null) {
items=new ItemsFragment(true);
items.setOnItemListener(this);
xaction
.add(R.id.second_pane, items)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.addToBackStack(null)
.commit();
}
else {
ContentFragment content=
(ContentFragment)fragMgr.findFragmentById(R.id.third_pane);
if (content!=null) {
xaction.remove(content).commit();
fragMgr.popBackStack();
}
}
I dont understand why its not being launched..When i click the item it does nothing on a tablet device.
EDIT:
private boolean isThreePane=false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FeedsFragment feeds
=(FeedsFragment)getSupportFragmentManager()
.findFragmentById(R.id.feeds);
feeds.setOnFeedListener(this);
isThreePane=(null!=findViewById(R.id.second_pane));
if (isThreePane) {
feeds.enablePersistentSelection();
}
}
I am not familiar with the Fragment API at all so this is total shot in the dark...
isThreePane=(null!=findViewById(R.id.second_pane));
is getting set to true on the tablet, so that means findViewById(R.id.second_pane) is not returning null. Do you have this second_pane view hard coded in your xml? How does the app decide whether or not the second_pane view should be present? Is it based on screen size at all?
Should you be using something like this:
fragMgr.findFragmentById(R.id.second_pane);
Instead of findViewById? Again I know nothing about fragments api, sorry if that is silly question.
Related
Ok so i'm building an android app that uses this library for a bottom navigation and i'm using a base Activity to hold it along with a Framelayout to manage my fragments for loading/replacing etc.
What works:
tapping on a bottom bar icon loads the fragment it corresponds to and that works perfectly.
My problem:
If i tap on the first tab and then the second tab and then the first tab AGAIN, the entire fragment reloads from scratch.
I don't want this behavior. Anyone have any good tips on how to retain the state of a fragment while ALSO using the bottom bar library.
I achieved something similar with a pagerview in a previous app (the previous app did not use a bottom bar for navigation) but I'm not sure how to use a pager view with ONE base activity that holds the Framelayout for replacing the fragments or if that is even the best solution.
I like the solution i have so far except that the fragments reload from scratch each time they replace the previous. If anyone has any help or suggestions that can help me out it would be greatly appreciated.
Alright i seemed to figure out a work around for the time being. It keeps the fragment state after switching tabs so I'm satisfied.
In the base activity class that hosts the fragment container i have the following
public class BaseActivity extends AppCompatActivity
{
AFragment AFragment = new AFragment();
BFragment BFragment = new BFragment();
Fragment currentFragment;
Boolean aIsActive = false;
Boolean bIsActive = false;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base);
BottomBar bottomBar = BottomBar.attach(this, savedInstanceState);
bottomBar.setItems(
new BottomBarTab(null,"A"),
new BottomBarTab(null,"B")
);
bottomBar.setDefaultTabPosition(0);
bottomBar.setOnTabClickListener(new OnTabClickListener()
{
#Override
public void onTabSelected(int position)
{
if (position == 0)
{
if(!aIsActive)
{
getSupportFragmentManager().beginTransaction().add(R.id.fragmentContainer,AFragment).commit();
aIsActive = true;
}
else
{
getSupportFragmentManager().beginTransaction().hide(currentFragment).show(AFragment).commit();
}
currentFragment = AFragment;
}
else if(position == 1)
{
if(!bIsActive)
{
getSupportFragmentManager().beginTransaction().add(R.id.fragmentContainer,BFragment).commit();
bIsActive = true;
}
else
{
getSupportFragmentManager().beginTransaction().hide(currentFragment).show(BFragment).commit();
}
currentFragment = BFragment;
}
}
#Override
public void onTabReSelected(int position) {
}
});
}
}
And loe and behold it works as expected without refreshing the fragments :)
any suggestions or feedback please let me know and feel free to comment.
I am working on an app that has a support Fragment with a SearchView widget and RecyclerView to present search results which sends the user to a FragmentActivity to display the details of the selection. All of this works fine, but I'm seeing inconsistent behavior between the Nexus 6 emulator and the actual device in regards to the backstack. In the emulator, everything works as I would expect, with the user being taken back to the search results fragment if pressing the Back button while on the details FragmentActivity. On the actual Nexus 6 device, the user is taken all the way back to the AppCompatActivity which contains my app's menu (this activity uses the support FragmentManager, which adds the fragments it manages to the backstack):
private void replaceFragment(Fragment supportFragment) {
String fragmentName = supportFragment.getClass().getSimpleName();
supportFragmentManager.beginTransaction()
.addToBackStack(fragmentName)
.replace(R.id.frame_menu_container, supportFragment, fragmentName)
.commit();
}
The code to send the user from the Fragment to the FragmentActivity is just an intent with extras:
Intent intent = new Intent(getActivity(), PlayerDetailsActivity.class);
intent.putExtra(TransientPlayer.BUNDLE_KEY, transientPlayer);
startActivity(intent);
I am not doing anything special with the backstack (yet) - this is with default behavior.
What could be the issue here? I've done some reading on the backstack and I haven't seen anything yet on manually adding something to the backstack when you're going back from a FragmentActivy to a Fragment where you're not using the FragmentManager.
EDIT 1: Here's the layout XML where frame_menu_container is defined:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"/>
<FrameLayout
android:id="#+id/frame_menu_container"
android:layout_below="#id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="#layout/fragment_home">
</FrameLayout>
</RelativeLayout>
EDIT 2: OK, here is a textual description of my high-level activities and fragments:
NavigationMenuActivity, which extends AppCompatActivity. This is where all the fragments are swapped in and out as needed, and the SupportFragmentManager is used and where I have the replaceFragment() method.
I have the following fragments, all of which either display static information, make REST calls to retrieve and display data, or allow the user to send feedback. All extend android.support.v4.app.Fragment, and none of them have other fragments or activities the user can go to.
HomeFragment
CalendarFragment
StandingsFragment
PlayerSearchFragment (see below)
AboutFragment
FeedbackFragment
The only exception in functionality for these fragments is the PlayerSearchFragment, which also extends android.support.v4.app.Fragment. This fragment contains a RecyclerView that displays the results of a REST call when users want to search for players. When results are returned and the user selects an item from the list, they are sent to a PlayerDetailsActivity which extends FragmentActivity.
The PlayerDetailsActivity uses a FragmentTabHost view which contains different types of information about the player that can be viewed. This is the "end of the line" down this path - there are no other fragments or activities the user can go to. This is where the issue is. When I hit the Back button while on this activity, my intention is to have the user go back to the PlayerSearchFragment fragment (search results), which in this case, they do if I'm in a Nexus 6 emulator, but they go all the way back to the NavigationMenuActivity activity if I'm on my actual Nexus 6 device.
I have the following method in the PlayerDetailsActivity which is called when the Back button is pressed, but it always shows zero entries:
#Override
public void onBackPressed() {
FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
Log.i(TAG, "Backstack : There are " + count + " entries");
for (int i = 0; i > count; i++) {
FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i);
Log.i(TAG, String.format("Backstack : id=%d, name=%s, shortTitle=%s, breadcrumbTitle=%s",
backStackEntry.getId(),
backStackEntry.getName(),
backStackEntry.getBreadCrumbShortTitle(),
backStackEntry.getBreadCrumbTitle()));
}
super.onBackPressed();
}
My hope is to have the experience be the same for all devices, whether hardware or emulator.
I think you want addToBackStack(fragmentName) called after replace(R.id.frame_menu_container, supportFragment, fragmentName).
Tell me what happens.
First thing according to your scenario you don't need to add transaction to BackStack as it's used only when you are navigating in between fragments in the same activity.
Now for your another point that when you back press you are not getting your fragment is you click two times. one click removes your second activity and another one is removing the fragment which is added in your backstack. As for your information Fragment dont have backpress callback, they rely on activity onbackPressed() method
I tried to run this code and it is running properly at my Nexus 5 device.
Although I'm attaching code what i am using
public class MainActivity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
replaceFragment(new FragmentA());
}
private void replaceFragment(Fragment supportFragment) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.frame_menu_container, supportFragment
.commit();
}
}
FragmentA is here:
public class FragmentA extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_a, null);
view.findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), AnotherActivity.class);
startActivity(intent);
}
});
return view;
}
}
And Another Activity is like:
public class AnotherActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.another_act);
}
}
I don't think that you should face any problem.
In case you still face problem tell me your device name and attach a code here so that i can test and will let you know.
well try this, hopefully will get you out of this error-hole.
This answer gives you a workaround but not what is really happening
#Override
public void onBackPressed() {
FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
Log.i(TAG, "Backstack : There are " + count + " entries");
if(count > 0){
fragmentManager.popBackStack(); //or popBackStackImmediate();
for (int i = 0; i > count; i++) {
FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i);
Log.i(TAG, String.format("Backstack : id=%d, name=%s, shortTitle=%s, breadcrumbTitle=%s",
backStackEntry.getId(),
backStackEntry.getName(),
backStackEntry.getBreadCrumbShortTitle(),
backStackEntry.getBreadCrumbTitle()));
}
}else{
super.onBackPressed();
}
}
While I still don't have an answer as to why I couldn't get this to work the way I had hoped, I do have a workaround, and it does work well for what I need to do. Basically, I override the Up and Back navigation callbacks in PlayerDetailsActivity and use an intent (with data) to explicitly send the user back to NavigationMenuActivity. The data I send in the intent is basically just a boolean that tells me if I'm coming from the PlayerDetailsActivity and if so, show that fragment rather than the Home fragment, like so:
#Override
public void onBackPressed() {
sendBackToMenu();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
sendBackToMenu();
finish();
return true;
}
return false;
}
private void sendBackToMenu() {
Intent backToMenuIntent = new Intent(this, NavigationMenuActivity.class);
backToMenuIntent.putExtra("FROM_PLAYER_DETAILS", true);
startActivity(backToMenuIntent);
}
The above changes were after I changed the class to extend from AppCompatActivity and added the following in PlayerDetailsActivity.onCreate() to activate the ActionBar for Up navigation:
ActionBar actionBar = getSupportActionBar();
assert actionBar != null;
actionBar.setTitle(R.string.player_details);
actionBar.setDisplayHomeAsUpEnabled(true);
Also, in NavigationMenuActivity.onCreate() I do this:
if (getIntent().getBooleanExtra("FROM_PLAYER_DETAILS", false))
replaceFragment(new PlayerSearchFragment());
else
replaceFragment(new HomeFragment());
I don't know if this is the most 'appropriate' way to do this, but it work well and does what I need across a handful of hardware devices and Nexus 4/5/6 emulators.
I've seen quite a few questions on SO about Fragments and I still can't seem to figure out if what I want to do is possible, and more so if my design pattern is just flawed and I need to re-work the entire process. Basically, like most questions that have been asked, I have an ActionBar with NavigationTabs (using ActionBarSherlock), then within each Tab there is a FragementActivity and then the FragmentActivities push new Fragments when a row is selected (I'm trying to re-create an iOS Project in Android and it's just a basic Navigation based app with some tabs that can drill down into specific information). When I click the back button on the phone the previous Fragment is loaded but the Fragment re-creates itself (so the WebServices are called again for each view) and this isn't needed since the information won't change in a previous view when going backwards. So basically what I want to figure out is how do I setup my Fragments so that when I push the back button on the phone, the previous Fragment is just pulled up with the previous items already created. Below is my current code :
//This is from my FragmentActivity Class that contains the ActionBar and Tab Selection Control
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
int selectedTab = tab.getPosition();
if (selectedTab == 0) {
SalesMainScreen salesScreen = new SalesMainScreen();
ft.replace(R.id.content, salesScreen);
}
else if (selectedTab == 1) {
ClientMainScreen clientScreen = new ClientMainScreen();
ft.replace(R.id.content, clientScreen);
}.....
//This is within the ClientMainScreen Fragment Class, which handles moving to the Detail Fragment
row.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//Do something if Row is clicked
try{
String selectedClientName = clientObject.getString("ClientName");
String selectedClientID = clientObject.getString("ClientID");
String selectedValue = clientObject.getString("ClientValue");
transaction = getFragmentManager().beginTransaction();
ClientDetailScreen detailScreen = new ClientDetailScreen();
detailScreen.clientID = selectedClientID;
detailScreen.clientName = selectedClientName;
detailScreen.clientValue = selectedValue;
int currentID = ((ViewGroup)getView().getParent()).getId();
transaction.replace(currentID,detailScreen);
transaction.addToBackStack(null);
transaction.commit();
}
catch (Exception e) {
e.printStackTrace();
}
}
});....
//And then this is the Client Detail Fragment, with the method being called to Call the Web Service and create thew (since what is displayed on this screen is dependent on what is found in the Web Service
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup group, Bundle saved) {
return inflater.inflate(R.layout.clientdetailscreen, group, false);
}
#Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//Setup Preferences File Link
this.preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
//initialize the table object
mainTable = (TableLayout)getActivity().findViewById(R.id.mainTable);
//setup the detail table
setupRelatedClientSection();
}
The Client Detail Screen can then drill down one more time, using the same method as the Client Main Screen but when I go back from that new screen to the Detail Screen the seuptRelatedClientSection() method is called again and so the entire Fragment is rebuilt when really I just want to pull up a saved version of that screen. Is this possible with my current setup, or did I approach this the wrong way?
Try using fragementTransaction.add instead of replace
I believe that you are looking for show() and hide().
I think you can still add them to the backstack.
transaction.hide(currentFragment);
transaction.show(detailScreen);
transaction.addToBackStack(null);
transaction.commit();
I didnt have my code to look at but i believe this is how it would go... Try it out unless someone else has a better way.
I have not tried the backstack with show() hide() but i believe that it takes the changes that are made before the transactions commit and will undo them if the back button is pressed. Please get back to me on this cause i am interested to know.
You also have to make sure that the detail fragment is created before you call this. Since it is based on the click of someitem then you should probably create the details fragment every time you click to make sure the correct details fragment is created.
I'm posting this answer for people who may refer this question in future.
Following code will demonstrate how to open FragmentB from FragmentA and going back to FragmentA from FragmentB (without refreshing FragmentA) by pressing back button.
public class FragmentA extends Fragment{
...
void openFragmentB(){
FragmentManager fragmentManager =
getActivity().getSupportFragmentManager();
FragmentB fragmentB = FragmentB.newInstance();
if (fragmentB.isAdded()) {
return;
} else {
fragmentManager.
beginTransaction().
add(R.id.mainContainer,fragmentB).
addToBackStack(FragmentB.TAG).
commit();
}
}
}
public class FragmentB extends Fragment{
public static final String TAG =
FragmentB.class.getSimpleName();
...
public static FragmentB newInstance(){
FragmentB fragmentB = new FragmentB();
return fragmentB;
}
#Override
public void onResume() {
super.onResume();
// add this piece of code in onResume method
this.getView().setFocusableInTouchMode(true);
this.getView().requestFocus();
}
}
In your MainActivity override onBackPressed()
class MainActivity extends Activity{
...
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
}
You're right, there has been a number of previous questions / documentation on the topic ;)
The documentation on Fragments, specifically the section about Transactions and Saving State, will guide you to the answer.
http://developer.android.com/guide/components/fragments.html#Transactions
http://developer.android.com/guide/components/activities.html#SavingActivityState
Android - Fragment onActivityResult avoid reloading
Fragments can have support for onSaveInstanceState but not onRestoreInstanceState, so if you want to save a reference to the table views, save them to the Bundle and you can access the saved view in your onActivityCreated method. You could also use the Fragments back stack.
This guide/tutorial has very detailed instructions/examples on the back stack and retaining fragment state.
Good luck
I am developing an application which involves various screen for tablet. On one side I have all the tabs like notification, messages and other using fragment. When any one click on any tabs it changes the other part of screen leaving tabs unaltered. Now problem is, when I click on a tab say notification there is a message showing that " you have 3 messages",(Which is written in text view), with messages as link to that screen which shows messages. How would i achieve that when any one click on message(written in text view), it should display screen with message. In simple word how to add a link to another screen using text view.
Any help will be appreciated. Thanks in advance. My code for main activity is as follows
public class MainActivity extends ActivityGroup
{
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
}
public void onNotificationsClick(View view)
{
setFragment(new NotificationsFragment());
}
public void onWorkListClick(View view)
{
setFragment(new WorkListFragment());
}
public void setFragment(Fragment fragment)
{
FrameLayout framelayout = (FrameLayout) findViewById(R.id.fl_container);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(framelayout.getId(), fragment).commit();
}
}
I solved it myself.
I just added in fragment_home.xml, *tv_alert_info_note text view* I added
android:clickable="true"
android:onClick="onMessageClick"
and like the same way added method "onMessageClick" in main activity class which is adding corresponding layout. that is
public void onMessageClick(View view)
{
setFragment(new TestFragment());
}
where TestFragment is fragment class.
EDIT
So it seems that my Fragment is retained in the FragmentManager which tries to reinitialize it. Still not sure why it isn't destroyed with the Activity. As for the loading message, this is displayed when the ListView does not have an adapter set. I however, set the adapter items in onCreate and onResume so I'm not sure why this loading screen is showing. Still open to any explanations of this behavior
Original
I'm playing around with fragments and noticed a weird error that is popping up when I change the orientation of the screen. This error should not be happening though because all the data is recreated in the onCreate when the screen orientation is changed. Also, the fragment onResume() is called twice after the rotation. Here's my steps for creating the error and how the debugger is hitting the functions.
Activity: onCreate()
Activity: onResume()
Fragment: onResume()
Rotate Screen
Activity: onCreate()
Activity: onResume()
Fragment: onResume() (items are null even though Activity.onResume() set them)
Fragment: onResume() (items are not null, why is this being called twice?)
After this last fragment onResume is hit, the tablet displays a "Loading..." message and icon. Why is the data not displayed in the list any more? My suspicions are that the onCreate is creating a second fragment. The first fragment looses its data because of the orientation destroying the views, the second fragment gets the data and the loading screen is the first fragment with no data items and the second fragment is hidden. I may be wrong. Why aren't the fragments all destroyed when the screen is rotated like the Activity? Please do not critique the code unless its to solve this specific issue. I'm not actually making an app, I'm experimenting with fragment functionality. Thanks!
Main Activity
private ArrayList<Object> items = new ArrayList<Object>();
private MyListFragment mylistFragment;
public MainActivity() {
items.add("Hello");
items.add("World");
items.add("Goodbye");
}
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
mylistFragment = new MyListFragment();
mylistFragment.setItems(items);
ft.add(R.id.container, mylistFragment);
ft.commit();
}
#Override
public void onResume() {
super.onResume();
mylistFragment.setItems(items);
mylistFragment.getListView().setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this, ((TextView)view).getText(), Toast.LENGTH_LONG).show();
}
});
}
List Fragment
private List<Object> items = null;
private Boolean isSet = false;
#Override
public void onResume() {
super.onResume();
if( !isSet && items != null) {
setListAdapter(new ArrayAdapter<Object>(getActivity(), R.layout.item, items));
isSet = true;
}
}
public void setItems(List<Object> items) {
this.items = items;
if( this.isResumed() ) {
setListAdapter(new ArrayAdapter<Object>(getActivity(), R.layout.item, items));
isSet = true;
} else {
isSet = false;
}
}
The FragmentManager will automatically recreate the Fragment for me. However, the data inside them needs to be reinitialized. Therefore my code was creating a second fragment. The loading screen was because the first fragment no longer had its adapter of items and the second fragment was hidden. What a headache, I almost think the FragmentManager should destroy the Fragments so you can recreate them with the data they hold.
I believe this is why in the various demos via Diane and other google examples... they typically have some way of helping hte initialization via a static method... and setArguments.
IIRC, the fragment creation stuff will call the default constructor to create, but only knows about populating with appropriate data via setArguments. In the example on the android development blog... the id was being passed from the listactivity, to the details fragment. After setParams is called, the fragmentManager should be able to reanimate it.
Documentation seems to be sparse on the subject and I'd like to be doing things right, so if you guys have feedback, I'd appreciate it.
It seems that using FragmentActivity from the support library saves and restores instance automatically. Therefore, only do your fragment transactions if savedInstanceState is null.
For example, in your FragmentActivity's onCreate(), do the following:
if(savedInstanceState == null){
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, mFragment).commit(); //mFragment is your own defined fragment
}