If I dynamically add a Fragment (using the FragmentManager) into a container defined in a landscape XML then switch to portrait, that dynamically injected Fragment still exists. It is contributing to the Action Bar even though it is not visible. What is a good way / design to prevent this from happening?
I have tried using isVisible in onCreateOptionsMenu of the Fragment but that causes issues on some Android versions because onCreateOptionsMenu is called before onCreateView which results in false even if the fragment is going to be visible with the current configuration.
Note: I am not handling the configuration myself. I haven't specified configChanges in the manifest and I am not overriding onConfigurationChanged.
Activity:
// inject detail fragment
Fragment detailFragment = getSupportFragmentManager().findFragmentById(R.id.detail_container);
if(detailFragment == null)
getSupportFragmentManager().beginTransaction().replace(R.id.detail_container, DetailFragment.newInstance(id)).commit();
// inject master fragment
if(findViewById(R.id.master_container) != null) {
masterDetail = true;
Fragment listFragment = getSupportFragmentManager().findFragmentById(R.id.master_container);
if(listFragment == null)
getSupportFragmentManager().beginTransaction().replace(R.id.master_container, ListFragment.newInstance(position)).commit();
}
Activity portrait XML
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Activity landscape XML
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:baselineAligned="false">
<FrameLayout
android:id="#+id/master_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<FrameLayout
android:id="#+id/detail_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"/>
</LinearLayout>
The model I'd follow based on your explanation.
public class MyActivity extends Activity {
boolean mMultiPane = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FrameLayout masterContainer = (FrameLayout) findViewById(R.id.master_container);
if (masterContainer != null)
mMultiPane = true;
...
}
}
From then on in your Activity you can use the mMultiPane variable to change the behaviour including changing how the options menu is set up (just have two different menu.xml files or add / remove menu items depending on which mode you're in).
Related
I have some sort of architecture issue:
I have Activity in which I need to show one of 3 views (one view at one moment in time):
1. One view with stable AppBarLayout + another part of the screen is
NestedScrollView that needs to be in separate fragment.
2. One view with stable AppBarLayout + another part of screen need to be a fragment with two tabs to switch between them.
3. Connection lost view - is a fullscreen view without AppBar - just to show that there is no connection.
The upper part of AppBarLayout - I made. Looks like good, but I need runtime to switch Fragments depending on the response from Server.
I am sending a request to the server in onCreate method in MainActivity, and then in onDataLoadedFromServer callback, depending on response - I am deciding which fragment to create. But my application crashes, because fragments fields are not initialized properly - the view is not attached to fragment. When I am adding fragment in onCreate method - everything is working good, but at that time I don't have a response from server.
Where to place TabLayout? And where ViewPager? Do I need to put
TabLayout in main_activity.xml, and ViewPager in separate
fragment_2_layout. And then add it to FrameLayout (container for
fragment which is located in main_activity)?
Is it good practice to load some data from the server in the fragment? Or it's better to load data in Activity and then set it through the method
call to Fragment?
Please, provide some sort of pseudo code to understand logic. Thanks!.
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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:id="#+id/shop_final_root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.lampa.letyshops.view.activity.ShopFinalActivity">
<android.support.design.widget.AppBarLayout>
<android.support.design.widget.CollapsingToolbarLayout>
<include
layout="#layout/collapsing_part" />
<android.support.v7.widget.Toolbar>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
<LinearLayout
android:id="#+id/content_layout_dual_tabs">
<android.support.design.widget.TabLayout />
<android.support.v4.view.ViewPager/>
</LinearLayout>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="#+id/fragment_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
<include layout="#layout/shop_final_no_connection_layout" />
</android.support.design.widget.CoordinatorLayout>
public MainActivity {
protected void onCreate(Bundle savedInstanceState) {
loadDataFromServer();
}
private void onDataLoaded(Object dto) {
if (showOneViewLayout) {
showOneViewLayout();
} else {
showTabViewLayout();
}
}
}
private void showOneViewLayout() {
f1 = Fragment1.newInstance(params);
showFragmentWithoutBackStack(R.id.fragment_holder, f1);
}
private void showTabViewLayout() {
f2 = Fragment2.newInstance();
showFragmentWithoutBackStack(R.id.fragment_holder, f2);
}
protected void showFragmentWithoutBackStack(int containerViewId, Fragment fragment) {
Fragment previousFragment = currentFragment;
Fragment currentFragment = fragment;
String fragmentTag = fragment.getClass().getSimpleName();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if (previousFragment != null) {
fragmentTransaction.hide(previousFragment);
}
fragmentTransaction.add(containerViewId, fragment, fragmentTag)
.commitAllowingStateLoss();
}
I've tried adding splash screens to my app, and while they do show before the main activity, this screen also always shows no matter what:
Here is the code for MainActivity:
public class MainActivity extends Activity implements WelcomeFragment.StartQuestions, QuestionFragment.QuestionsAnswered {
#Override
protected void onCreate(Bundle savedInstanceState) {
Parse.enableLocalDatastore(this);
Parse.initialize(this, "***************************", "*****************************");
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
loadingIcon = (ProgressBar) findViewById(R.id.loadingIcon);
loadingIcon.setVisibility(View.GONE);
checkInternetConnection();
showWelcomeScreen();
}
}
showWelcomeScreen():
public void showWelcomeScreen(){
Fragment fragment = getFragmentManager().findFragmentById(R.id.fragmentContainer);
if (fragment == null){
fragment = new WelcomeFragment();
getFragmentManager().beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
} else {
fragment = new WelcomeFragment();
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, fragment)
.commit();
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="#+id/loadingPanel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" android:id="#+id/loadingIcon"/>
</RelativeLayout>
In MainActivity, I have already specified the removal of the top bar:
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
The app itself doesn't have the bar. Any activity that I place as being default in the manifest (acting as a splash screen) is nevertheless proceeded by the screen shown above when the app is launched.
You can't. Android will always attempt to show something using only the attributes of your theme before your Activity actually loads.
In fact, the correct way to build a splash screen involves taking advantage of that fact and customizing your theme such that what displays during this time is your splash screen.
I have a requirement for my app that a custom action bar view be shown from one fragment only (the landing page fragment). The problem is that this action bar is appearing when the user navigates to other fragments. Is there a way to do this without disabling custom view on every fragment?
Thanks
For this issue, only show actionbar for one fragment without show on all other fragments, solution could be very easy:
In your activity that hosts your fragments:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
... ...
// for API >= 11
getActionBar.hide();
// for API < 11
getSupportActionBar().hide();
... ...
}
Then in the fragment that you want to show actionbar:
#Override
public void onAttach(Activity activity){
activity.getActionBar().show(); //getSupportActionBar() for API<11
}
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
getActivity().getActionBar().hide(); //getSupportActionBar() for API<11
} else {
getActivity().getActionBar().show(); //getSupportActionBar() for API<11
}
}
Well, I'm just trying to suggest. Why not removing the action bar to all the fragments and just creating a single fragment (the landing page) with a custom action bar thru layout?
To remove ActionBar to all fragments, add this code to tag:
android:theme="#android:style/Theme.Light.NoTitleBar"
Create a Layout with action bar for your landing page through xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/rlMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true" >
<RelativeLayout
android:id="#+id/rlHeaderMain"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentTop="true"
android:background="#000000"
android:clipChildren="false"
android:clipToPadding="false"
android:padding="5dp" >
</RelativeLayout>
I have to implement "standart" fragments navigation in my app (please see link).
The issue is when device is in portrait mode, there should be shown only 1 fragment, and when it is rotated to landscape mode, 2 fragments should be shown.
I tried to do this 2 different ways:
1) I use only 1 activity with different portrait and landscape layouts.
Portrait layout xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="#+id/main_frame_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
And here`s landscape layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="horizontal" >
<FrameLayout
android:id="#+id/main_frame_fragment_container_left"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" />
<FrameLayout
android:id="#+id/main_frame_fragment_container_right"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
Activity`s onCreate method:
private static ItemsFragment mItemsFragment;
private static ItemDetailsFragment mItemDetailsFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (mItemsFragment == null) {
mItemsFragment = ItemsFragment.newInstance();
}
if (mItemDetailsFragment == null) {
mItemDetailsFragment = ItemDetailsFragment.newInstance();
}
if (isLandscape()) {
getSupportFragmentManager().beginTransaction().replace(R.id.main_frame_fragment_container_left, mItemsFragment)
.commit();
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_frame_fragment_container_right, mItemDetailsFragment).commit();
} else {
getSupportFragmentManager().beginTransaction().replace(R.id.main_frame_fragment_container, mItemsFragment)
.commit();
}
}
And that`s the way I refresh 2nd fragment:
Bundle bundle = new Bundle();
bundle.putSerializable(BaseFragment.KEY_BUNDLE_ITEM, response.getItem());
mItemDetailsFragment = ItemDetailsFragment.newInstance(bundle);
if (isLandscape()) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_frame_fragment_container_right, mItemDetailsFragment).commit();
} else {
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_frame_fragment_container, mItemDetailsFragment).addToBackStack(null).commit();
}
Also I save and restore fragments` states, so my data does not disappear after rotations. Generally, this code works properly in my case.
2) I use 2 activities and the same layout for 1st Activity portrait and landscape modes.
xml layout is the same as in previous one for landscape:
<FrameLayout
android:id="#+id/main_frame_fragment_container_left"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" />
<FrameLayout
android:id="#+id/main_frame_fragment_container_right"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
onCreate method (note, that fragments entities are not static, as it was in 1st case):
private ItemsFragment mItemsFragment;
private ItemDetailsFragment mItemDetailsFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
mItemsFragment = ItemsFragment.newInstance();
mItemDetailsFragment = ItemDetailsFragment.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.main_frame_fragment_container_left, mItemsFragment)
.commit();
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_frame_fragment_container_right, mItemDetailsFragment).commit();
}
}
And now if the device is in portrait mode, I start new Activity:
if (isLandscape()) {
Bundle bundle = new Bundle();
bundle.putSerializable(BaseFragment.KEY_BUNDLE_ITEM, response.getItem());
mItemDetailsFragment = ItemDetailsFragment.newInstance(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_frame_fragment_container_right, mItemDetailsFragment).commit();
} else {
Intent intent = new Intent(getApplicationContext(), DetailsActivity.class);
intent.putExtra(KEY_ITEM, response.getItem());
startActivity(intent);
}
And, at last, 2nd Activity`s onCreate method:
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
setContentView(R.layout.activity_details);
if (isLandscape()) {
finish();
}
Item item = (Item) getIntent().getExtras().getSerializable(KEY_ITEM);
Bundle bundle = new Bundle();
bundle.putSerializable(BaseFragment.KEY_BUNDLE_ITEM, item);
ItemDetailsFragment mItemDetailsFragment = ItemDetailsFragment.newInstance(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.main_frame_fragment_container, mItemDetailsFragment).commit();
}
When device is rotated to landscape mode, 2nd activity finishes, and I see my 1st activity with 2 fragments (as expected).
Question:
In 1st case I save fragments as static variables, and because of this I don't care if I change 2nd fragment state in portrait or landscape modes (the same fragment is used). But I don't think it's a good idea to save it as static fields.
In 2nd case I don't know how to sync Activity A Fragment B (landscape) and Activity B Fragment B (portrait). If I change something in fragment (I mean, toggle button etc) and rotate device, changes should be applied in another fragment.
Generally, what case is better, and if 2nd, how can I resolve synchronization issue? Or maybe there is another easier way. Thanks for reading, I hope you can help me :)
Just follow this mate http://developer.android.com/guide/components/fragments.html. Dont make fragments static (thats just wierd)
findViewById(R.id.team_detail_container) is failing to find the view. Is my problem with the xml or in the way I am constructing the FragmentActivity? How do I solve this?
To support a customised list for a fragment in the constructor of my fragment activity I have replaced
setContentView(R.layout.activity_team_list);
with
frag=(TeamListFragment)getSupportFragmentManager().findFragmentById(android.R.id.content);
if (frag==null) {
frag=new TeamListFragment();
getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit();
So that my class now looks like this
public class TeamListActivity extends SherlockFragmentActivity implements
TeamListFragment.Callbacks {
private boolean mTwoPane;
private TeamListFragment frag=null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
frag=(TeamListFragment)getSupportFragmentManager().findFragmentById(android.R.id.content);
if (frag==null) {
frag=new TeamListFragment();
getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit();
}
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (findViewById(R.id.team_detail_container) != null) {
Log.i("####", "Team Detail Container has been found! Yaay!");
mTwoPane = true;
((TeamListFragment) getSupportFragmentManager().findFragmentById(
R.id.team_list)).setActivateOnItemClick(true);
}
}
The if (findViewById(R.id.team_detail_container) != null) { condition is never met despite the fact that team_detail_container exists in the xml file that is used
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/team_list_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:orientation="horizontal"
android:showDividers="middle"
tools:context=".TeamListActivity" >
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/team_list"
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<ListView
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawSelectorOnTop="false" />
<TextView android:id="#id/android:empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="No data"/>
</LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
/>
<FrameLayout
android:id="#+id/team_detail_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3" />
</LinearLayout>
I know that this particular layout xml is being used as changes I make to the xml file are reflected in the app.
I also know that the condition is never met as I never get the log message Log.i("####", "Team Detail Container has been found! Yaay!"); and the behaviour get is that when an item is selected the list view is replaced with the detail fragments instead of the detail fragments being displayed next to the list view because the item selected conditions are not being met
#Override
public void onItemSelected(int id) {
if (mTwoPane) {
// mTwoPane is never set! Why?
Bundle arguments = new Bundle();
arguments.putInt(TeamDetailFragment.ARG_ITEM_ID, id);
TeamDetailFragment fragment = new TeamDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.replace(R.id.team_detail_container, fragment).commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, TeamDetailSwipeActivity.class);
detailIntent.putExtra(TeamDetailFragment.ARG_ITEM_ID, id);
startActivity(detailIntent);
}
}
The fragment doesn't get added to your layout tree immediately after committing the transaction. You'll have to wait till onViewCreated on the fragment was called. You could probably access the view within the onStart-Method of your Activity, but it would probably be a better idea to keep that logic within the fragment itself. The Activity shouldn't be concerned with what views are contained in a fragment.
I think the issue is you're looking for the Fragment from the Activity, but since you're not using setContentView, the Activity doesn't actually have a View to look through. Try getting the view from the Fragment instead using the getView() method.