getSupportActionBar from inside of Fragment ActionBarCompat - android

I'm starting a new project that uses the AppCompat/ActionBarCompat in v7 support library. I'm trying to figure out how to use the getSupportActionBar from within a fragment. My activity that hosts the fragment extends ActionBarActivity, but I don't see a similar support class for Fragments.
From within my fragment
public class CrimeFragment extends Fragment {
//...
getActivity().getSupportActionBar().setSubtitle(R.string.subtitle); // getSupportActionBar is not defined in the v4 version of Fragment
//...
}
The google page for using it (http://android-developers.blogspot.in/2013/08/actionbarcompat-and-io-2013-app-source.html) says there should be no changes for the v4 fragment. Do I need to cast all my getActivity() calls to an ActionBarActivity? That seems like poor design.

After Fragment.onActivityCreated(...) you'll have a valid activity accessible through getActivity().
You'll need to cast it to an ActionBarActivity then make the call to getSupportActionBar().
((AppCompatActivity)getActivity()).getSupportActionBar().setSubtitle(R.string.subtitle);
You do need the cast. It's not poor design, it's backwards compatibility.

While this question has an accepted answer already, I must point out that it isn't totally correct: calling getSupportActionBar() from Fragment.onAttach() will cause a NullPointerException when the activity is rotated.
Short answer:
Use ((ActionBarActivity)getActivity()).getSupportActionBar() in onActivityCreated() (or any point afterwards in its lifecycle) instead of onAttach().
Long answer:
The reason is that if an ActionBarActivity is recreated after a rotation, it will restore all Fragments before actually creating the ActionBar object.
Source code for ActionBarActivity in the support-v7 library:
#Override
protected void onCreate(Bundle savedInstanceState) {
mImpl = ActionBarActivityDelegate.createDelegate(this);
super.onCreate(savedInstanceState);
mImpl.onCreate(savedInstanceState);
}
ActionBarActivityDelegate.createDelegate() creates the mImpl object depending on the Android version.
super.onCreate() is FragmentActivity.onCreate(), which restores any previous fragments after a rotation (FragmentManagerImpl.dispatchCreate(), &c).
mImpl.onCreate(savedInstanceState) is ActionBarActivityDelegate.onCreate(), which reads the mHasActionBar variable from the window style.
Before mHasActionBar is true, getSupportActionBar() will always return null.
Source for ActionBarActivityDelegate.getSupportActionBar():
final ActionBar getSupportActionBar() {
// The Action Bar should be lazily created as mHasActionBar or mOverlayActionBar
// could change after onCreate
if (mHasActionBar || mOverlayActionBar) {
if (mActionBar == null) {
... creates the action bar ...
}
} else {
// If we're not set to have a Action Bar, null it just in case it's been set
mActionBar = null;
}
return mActionBar;
}

If someone uses com.android.support:appcompat-v7: and AppCompatActivity as activity then this will work
((AppCompatActivity)getActivity()).getSupportActionBar().setSubtitle(R.string.subtitle);

For those using kotlin,
(activity as AppCompatActivity).supportActionBar.setSubtitle(R.string.subtitle)

As an updated answer for Pierre-Antoine LaFayette's answer
ActionBarActivity is deprecated; use AppCompatActivity instead
((AppCompatActivity)getActivity()).getSupportActionBar();

in your fragment.xml add Toolbar Tag from support library
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" />
Now how we can control it from MyFragment class? let's see
inside onCreateView function add the following
mToolbar = (Toolbar) view.findViewById(R.id.toolbar);
((AppCompatActivity)getActivity()).setSupportActionBar(mToolbar);
//add this line if you want to provide Up Navigation but don't forget to to
//identify parent activity in manifest file
((AppCompatActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
and if you want to add items to the toolbar within MyFragment
you must add this line inside onCreateView function
setHasOptionsMenu(true);
this line is important, if you forget it, android will not populate your menu Items.
assume we identify them in menu/fragment_menu.xml
after that override the following functions
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.fragment_menu, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.action_1:
// do stuff
return true;
case R.id.action_2:
// do more stuff
return true;
}
return false;
}
hope this helps

As an addendum to GzDev's answer, if you already have the string, you can use kotlin's auto-setter:
(activity as AppCompatActivity).supportActionBar?.subtitle = my_string
And you can turn it off by simply using an empty string.
Note that this works for both the title and the subtitle.

Related

How to reuse the code in android

I'm trying to reduce the double effort of using the duplicate code. I searched on the google but didn't found about it that how we can reduce the duplication of code in android. May be this question could be stupid but I want the clarification.
1) The first thing I want to ask is that how we can reuse the same code in the multiple activity which are being using in overriden method as onBackPressed() onOptionsItemSelected() and so on. Here is the code which I'm currently writing in the onBackPressed() method.
#Override
public void onBackPressed(){
if(this.mDrawerLayout.isDrawerOpen(GravityCompat.START)){
this.mDrawerLayout.closeDrawer(GravityCompat.START);
}else{
super.onBackPressed();
}
}
For this approache I get the suggestion from this question to make the base activity then override this method after that extends other activities from that BaseActivity. But how I can pass the mDrawerLayout field in the BaseActivity? how I can use findViewById() on that base activity to access the xml widgets to access in the overriden method as currently using mDrawerLayout layout.
Example in code.
public BaseActivity extends AppCompatActivity{
private DrawerLayout mDrawerLayout; // how to initialize it? where to call findViewById?
#Override
public void onBackPressed(){
if(this.mDrawerLayout.isDrawerOpen(GravityCompat.START)){
this.mDrawerLayout.closeDrawer(GravityCompat.START);
}else{
super.onBackPressed();
}
}
}
The same question for the onOptionsItemSelected() mean menu item actions.
2) The second duplication I'm facing about hundred of time is startActivity() I have to write Intent then add the extra data if required than use that Intent. so about 2 to 3 lines I have to write again and again.
3) This thing is about the XML, I'm using the value 5dp or 10dp or 10sp or other dimen values in the XML file which are repeating a lot of time. So I want to ask is this approach will be Ok?
<dimen name="ten_dp">10dp</dimen>
android:layout_margin="#dimen/ten_dp"
Mean declare the dp value then use that in the XML.
Edited:
4) This problem I have faced now. I'm using the same toolbar in all activities and after adding it in XML layout I have to write this code.
private void setupToolbar(){
setSupportActionBar(mainToolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
}
}
But I have to re-write/copy the code in all activities. What is the solution of this problem?
1) For your first question, you'd have to have mDrawerLayout in your BaseActivity, then you'd attach your child activity's DrawerLayout into that field on the child activity's onCreate():
super.mDrawerLayout = findViewById(R.id.drawer_layout)
You can also do that for other #Override methods, such as onOptionsItemSelected().
Example based on your BaseActivity class:
public class MainActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDrawerLayout = findViewById(R.id.drawer_layout);
}
}
Also, make your BaseAcitivity abstract to avoid the "add to manifest warning":
public abstract class BaseActivity extends AppCompatActivity
2) As for your second question, I'd say that in your BaseActivity you could have a public method called getIntent() which would return a common Intent object. You'd then call that method from your child Activity.
You can pretty much have any method with common functionalities on your BaseActivity.
3) From my personal experience, it is counterproductive to declare resource values that does not represent the purpose of itself, such as yours: <dimen name="ten_dp">10dp</dimen>. I'd rather name it based on a representative use, such as <dimen name="activity_margin">10dp</dimen> or whatever.
It is a good practice indeed to have all your hardcoded values (strings, dimens, colors, etc) on your XML resources, you just need to give them significant names (treat them as variable names).
Edit:
4) Add that method into your BaseActivity and pass the Toolbar as a param:
private void setupToolbar(Toolbar mainToolbar){
setSupportActionBar(mainToolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
}
}
Then, you can call it from your MainActivity as:
Toolbar mainToolbar = findViewById(R.id.main_toolbar);
setupToolbar(mainToolbar);
1- How to get reference to the drawer:
If all activities have drawer in its xml, and with the same Id, then you can use findViewById in BaseActivity.
protected DrawerLayout drawerLayout;
protected void initNavDrawer() {
drawerLayout = findViewById(R.id.drawer);
}
The BaseActivity is actually the current running activity, So findViewById is totally fine but make sure you have the drawer in all activity, else you will face NullPointerException
2- For starting activity, In each activity I add a static method which opens the activity
public static void openForEdit(#NonNull Activity context, #NonNull Order order, int requestCode) {
Intent editOrderIntent = new Intent(context, EditOrderActivity.class);
editOrderIntent.putExtra(ORDER_TAG, order);
editOrderIntent.putExtra(EDIT_TAG, true);
context.startActivityForResult(editOrderIntent, requestCode);
}
You can make more than one method if needed.
Then wherever you want to open the activity just call the static method
MyActivity.open(//passing parameters);
3- About the third point I didn't get exactly your point, But just for clarification: You need to create new xml file called dimens.xml, then you declare your dimens
4- You can put this method in BaseActivity and call it from onCreate in BaseActivity

How to change Android NavigationView's selected item in relation to fragment's popBackStack

I implemented the new NavigationView (from the support library) and a couple of Fragments, I then overrided the onBackPressed function and added this:
getFragmentManager().popBackStack();
which returns me to the previous fragment.
So what I need is this: how I can change the navigationview's current selected item to the Fragment it is popping back to?
Finally i got it,
Firstly, i made the instance of the navigation global and public.
public NavigationView navigationview;
I then added in the onCreate and onResume of my fragment:
NavigationView navigationView = ((MainActivity) getActivity()).navigationview;
navigationView.getMenu().getItem(index).setChecked(true);
and the onResume:
navigationView.getMenu().getItem(index).setChecked(true);
This solved my problem, Hope this helps someone.
I know this is a little late but I found a solution that is maybe a little faster to code for Activities that have a large amount of fragments.
(In your activity)
Implement FragmentManager.OnBackStackChangedListener
In onCreate() add fragmentManager.addOnBackStackChangedListener(this);
Add Method from interface...
#Override
public void onBackStackChanged() {
MainActivityFragment f = (MainActivityFragment) fragmentManager.findFragmentById(R.id.fragment_container);
//Log.v(TAG, "OnBackStackChanged to fragment with index: " + f.getMenuIndex());
navigationView.getMenu().getItem(f.getMenuIndex()).setChecked(true);
}
(Create an interface in a separate file, name it MainActivityFragment) or whatever your activity is named.
public interface MainActivityFragment {
int getMenuIndex();
}
(All fragments that are used in that Activity)
Implement MainActivityFragment
Add Method from interface...
#Override
public int getMenuIndex() {
return 2;
}
It may seem like some extra work but in the long run it makes it pretty easy as all you have to do for new Fragments is implement the Activity's Interface and return the index of it's menu item.

How to implement onCreateOptionsMenu using AppCompat / Toolbar and Fragments

I have an Activity that extends ActionBarActivity and uses the new Toolbar, within the activity i populate my Context menu and when it shows Toolbar is correctly hidden because i use the following:
<item name="windowActionModeOverlay">true</item>
Then i create a Fragment dynamically, and the Fragment also has options using:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
...
}
Then since i have a list i add a contextmenu this way:
mListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
...
}
The problem is that the Fragment Context menu seems to behave differently, i need to use "android:windowActionModeOverlay" in order to hide the Toolbar automatically and also its ignoring the appcompat parameters like "app:showAsAction".
How can i correctly implement appcompact Context menu within a Fragment? I saw that in the sources of the Appcompat library there was an ActionBarFragment once but its not in the master branch or in the released library.
I am not using a drawer (yet).
Found the issue myself, instead of using:
mActionMode = getActivity().startActionMode(contextMenuListener);
I had to use:
mActionMode = getActivity().getSupportActionBar().startActionMode(contextMenuListener);
And off course implement support library ActionMode instead of the builtin one.

Android findViewById(android.R.id.home) return null with support library

I want to change ActionBar home button left padding. I've tried this solution. But when I try findViewById(android.R.id.home) I get null. In the same time android.R.id.home is not 0.
And this happens only if I use android-suppot-v7. If I don't use support library all goes good.
Maybe someone can help me?
Here is my simple code:
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar = getSupportActionBar();
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
ImageView view = (ImageView)findViewById(android.R.id.home);
if (view !=null){
view.setPadding(10, 0, 0, 0);
}
}
}
Layout:
<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"
tools:context="com.example.timoshkois.actionbar.MainActivity">
</RelativeLayout>
Hmm you're not wrong, if you look at the source for the Activity you inherit from, they also use android.R.id.home
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
Like this
#Override
public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) {
if (super.onMenuItemSelected(featureId, item)) {
return true;
}
final ActionBar ab = getSupportActionBar();
if (item.getItemId() == android.R.id.home && ab != null &&
(ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
return onSupportNavigateUp();
}
return false;
}
Looking how the ActionBar is created it uses these classes:
https://github.com/android/platform_frameworks_support/blob/5476e7f4203acde2b2abbee4e9ffebeb94bcf040/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
https://github.com/android/platform_frameworks_base/blob/master/core/java/com/android/internal/app/WindowDecorActionBar.java
which leads to the ActionBar class, this has a possible clue about why it returns null
https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/ActionBar.java#L106
/**
* Standard navigation mode. Consists of either a logo or icon
* and title text with an optional subtitle. Clicking any of these elements
* will dispatch onOptionsItemSelected to the host Activity with
* a MenuItem with item ID android.R.id.home.
*
* #deprecated Action bar navigation modes are deprecated and not supported by inline
* toolbar action bars. Consider using other
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
This deprecation means the new ToolBar won't use nav modes so maybe this also means Toolbar will not have this Android id (R.id.home) - which makes sense as the previous links show that app compat not uses a Toolbar under the hood, which legacy implementations will not be using.
As a test you could do what the comment says and override onOptionsItemSelected press the logo and query the view you are passed to find it's id getId()
Apparently, 'home' is the name of your layout file? [No, I see that's activity_main.] To access the relativelayout item, it needs an ID of its own, like android:id="#+id/home"

How to hide action bar for fragment?

How can I hide action bar for certain fragment?
I have searched for the answer at stackoverflow, but I have only found a solution, which involves disabling action bar for main activity in android manifest. Since I need to disable action bar for one fragment, this is not an option.
Any ideas? Thanks.
EDIT: min API level is 7, sherlock is not being used
If you are using AppCompatActivity (you should) then this is the solution that worked for me:
((AppCompatActivity) getActivity()).getSupportActionBar().hide();
You can add this in onCreate(). The support fragment's getActivity() returns a FragmentActivity, and this does not contain the getSupportActionBar() method. Using just getActionBar() gives null-pointer exception if you have AppCompatActivity.
Put this code in fragment in which you want to hide toolbar...
#Override
public void onResume() {
super.onResume();
((AppCompatActivity)getActivity()).getSupportActionBar().hide();
}
#Override
public void onStop() {
super.onStop();
((AppCompatActivity)getActivity()).getSupportActionBar().show();
}
As it was already mentioned, actionbar may be hidden by (requireActivity() as AppCompatActivity).supportActionBar?.hide() call.
If you want to show it in some of your fragments and to hide it in some other fragments, it may be convenient to apply default (for your case) visibility in onViewCreated of your Base fragment:
abstract class BaseFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(requireActivity() as AppCompatActivity).supportActionBar?.show()
}
}
and to hide it in particular fragments:
class HiddenActionBarFragment : BaseFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(requireActivity() as AppCompatActivity).supportActionBar?.hide()
}
}
This solution is more flexible than using onStart - onStop for visibility change because during the transition onStart of a different fragment will be called earlier than onStop of the current fragment. So the next fragment won't be able to 'override' actionar visibility applied in onStop of the current fragment.
getActionBar().hide() or getSupportActionBar().hide() (if using ActionBarCompat v7 lib).
Regards
Hide actionBar using this in the required fragment.
getActivity().getSupportActionBar().hide();
And Show actionBar with this in your next fragment.
getActivity().getActionBar().show();
Put getSupportActionBar().hide() before setContentView in the Activity that holds the fragments.
Also add this: ((AppCompatActivity) getActivity()).getSupportActionBar().hide() in the fragments before inflating layout. This works if you are using this ActionBarActivity.It also removes my lags in hiding action bar
You can simply put this into your Fragment Class createView method:-
View layout = inflater.inflate(R.layout.player_fragment, container, false);
((AppCompatActivity) getActivity()).getSupportActionBar().hide();
Paste the code in the fragments before inflating the layout to SHOW Action Bar `
//Kotlin statement
(activity as AppCompatActivity?)!!.supportActionBar!!.show()
Paste the code in the fragments before inflating the layout to Hide Action Bar `
//Kotlin statement
(activity as AppCompatActivity?)!!.supportActionBar!!.hide()
This works fine for me.
Have you tried getActivity().getSupportActionBar().hide() in the onCreate() of the fragment you wish the ActionBar to be hidden in?
I am assuming you are not using ActionBarSherlock.
If you're like me and have one main Activity and several Fragments, you can add a listener to your NavController in the main Activity's onCreate() method.
For instance, I only wanted it hidden in my Login Fragment, so I used this (Kotlin):
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == R.id.loginFragment) {
supportActionBar?.hide()
} else {
supportActionBar?.show()
}
}
This avoids having to add the call to show() in all the Fragments in which you do want the ActionBar.
This solution is for complex non-AppCompat applications that use native ToolBar when running Lollipop onwards and native ActionBar otherwise.
It assumes you want to hide the ActionBar whenever Fragments are visible.
Inside onCreate() in each of your Activities:
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener()
{
#Override
public void onBackStackChanged() {
U.ABkk(this, getFragmentManager().getBackStackEntryCount());
}
}
);
OR (much better) inside a 'singleton' class that implements Application.ActivityLifecycleCallbacks
#Override
public void onActivityCreated(final Activity A, Bundle savedInstanceState) {
A.getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
U.ABkk(A, A.getFragmentManager().getBackStackEntryCount());
}
});
}
Inside the utility class:
/** Show/hide ActionBar for KitKat devices */
public static void ABkk(Activity A, int count) {
if (lollipop()) return; // No problem when using Toolbar
ActionBar ab = A.getActionBar();
if (ab==null) return;
if (count==1) { ab.hide(); }
if (count==0) { ab.show(); }
}
/** Return true if API 21 or greater */
private static boolean lollipop() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
Using onActivityCreated() is a solution that requires no changes to your Fragments or Activities!

Categories

Resources