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
Related
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.
I want my app to show the side navigation drawer as soon as the main activity is created.
My code works fine - user launches app and gets the open drawer - but I'd like to actually see the side drawer sliding from the left; instead, I find the drawer fully opened.
At what point should I call openDrawer()?
Have tried calling from:
main activity OnCreate;
similar points in the fragment hosted by the drawer.
I could try OnPrepareOptionsMenu, but I think it gets called more than once during the activity lifecycle. I also tried OnStart() and I fear my options are over.
Any idea? I'm sure this is pretty simple but I can't figure out.
Edit: I realize I wasn't so clear with my first exposition of the question (#Biu). I'm talking about a purely graphical issue here. The point is:
I have something to happen at startup; in my case we're speaking about the nav drawer sliding into the main screen, but it could be any animation I think;
In my case, one could just call:
protected void OnCreate(Bundle b) {
...
DrawerLayout.openDrawer()
}
The above solution works well. The issue I'm talking about is graphical; with the above code you launch the app and find the main activity covered with an already-opened drawer. Instead, I'd like the user to have clue of what is happing, to see where the panel came from; in other words, to see the opening animation.
So my question is: when should I call openDrawer()? The main Activity onCreate isn't quite right, because the animation ends before the user gets to see something on screen.
I thought that the wish of having something start when all is loaded would be more common.
#benjosantony suggests that you should open your drawer at onResume, however it's not guaranteed that the activity will be visible at that time:
onResume is not the best indicator that your
activity is visible to the user; Use onWindowFocusChanged(boolean) to know for certain
that your activity is visible to the user
You'd think that you can just use onWindowFocusChanged and be done, but you can't. There's still the transition animation which breaks (at least for me) the drawer's animation..
For API 21+:
There's onEnterAnimationComplete where you can open your drawer and see the animation properly. However 21+ is a requirement that's just too big..
For lower APIs:
The only possible way I can think of is removing the activity's animation with a theme adjustment:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowAnimationStyle">#null</item>
</style>
And opening the drawer like so:
private static final String DRAWER_STATE = "mDrawerOpened";
private DrawerLayout mDrawer;
private ListView mDrawerList;
private boolean mDrawerOpened;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerList = (ListView) findViewById(R.id.left_drawer);
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (!mDrawerOpened && hasFocus) {
mDrawer.openDrawer(mDrawerList);
mDrawerOpened = true;
}
}
#Override
protected void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(DRAWER_STATE, mDrawerOpened);
}
#Override
protected void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mDrawerOpened = savedInstanceState.getBoolean(DRAWER_STATE);
}
This will animate the drawer only when the activity is started.
The boolean value is saved when your activity is destroyed abnormally, e.g. rotation or need for system resources.
If you don't like setting the instanceState you can use SharedPreferences as #Biu suggested, however IMO that wouldn't be the proper solution as android already provides tools for that, there's no need to re-invent the bike.
You could use this hack by using SharedPreferences
boolean firstTime = true;
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
public void onCreate(Bundle bundle)
{
if (pref.getBoolean("firstTime", true) == true)
{
drawer.openDrawer(yourDrawer);
firstTime = false;
pref.editor().putBoolean("firstTime", firstTime).apply();
}
}
Activity
The foreground lifetime of an activity happens between a call to onResume() until a corresponding call to onPause(). During this time the activity is in front of all other activities and interacting with the user.
Thus I think onResume is the best place to open your drawer.
Whats the efficient way to add Navigation Drawer on all the activities? I don't want to repeat the code for Navigation Drawer in all the activities and their layouts. Is it possible somehow to add Nav. Drawer in BaseActivity(custom class) and then every other activity will extend BaseActivity inorder to have the Navigation Drawer ?
Is it possible somehow to add Nav. Drawer in BaseActivity(custom class) and then every other activity will extend BaseActivity inorder to have the Navigation Drawer ?
Yes this is definitly the cleanest way to go.
public BaseActivity extends Activity {
#Override
protected void onCreate()
super.onCreate(); // calls Activity.onCreate()
// setup your Navigation Drawer
}
public FirstActivity extends BaseActivity {
#Override
protected void onCreate()
super.onCreate(); // will call the BaseActivitiy.onCreate()
// do something in the FirstActivity
}
public SecondActivity extends BaseActivity {
#Override
protected void onCreate()
super.onCreate(); // will call the BaseActivitiy.onCreate()
// do something in the SecondActivity
}
The "hard work" will be the layouts. Have one baseLayout for the BaseActivity with a place holder for the Content View (the visible part of the Activities). For all other Activitys use this layout and include your Content View.
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.
Im trying to change the title from the activities inside the Tab. but the title remains same as the Title given for TabActivity. I searched for the solution but not succeeded yet. is there a way to do it? please help me on this.
Thanks
In an Activity used as child of a TabActivity you can use simply
getParent().setTitle("your title");
the getParent will return the current instance of TabActivuty, than you can change the title
You could use the singleton methodology with the TabActivity, then use This Method to change the title.
MyTabs.java
class MyTabs extends TabActivity {
private static MyTabs theInstance;
public static getInstance() {
return MyTabs.theInstance();
}
public MyTabs() {
MyTabs.theInstance = this;
}
...
}
ActivityInTab.java
...
TabActivity tabActivity = MyTabs.getInstance();
((TextView)tabActivity.getTabHost().getTabWidget().getChildAt(0).findViewById(android.R.id.title)).setText("New");
...
(Obviously this isn't a complete singleton implementation, but it will suffice for what you're doing. Since the activity can't exist without the parent container, it's safe to assume a reference has been set in the constructor when the object was created.)