I am using a TabActivity (Main) with 3 TabSpecs
I am using Intents for the content of the 3 Tabs
TabA, TabB, TabC for example.
All these tab activities use common data that is stored in SharedPreferences
In the Main TabActivity I have an options menu which has a refresh option.
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.refresh_menu:
new updateCommonDataFromWeb().execute();
...
}
}
this refresh uses an AsyncTask (updateCommonDataFromWeb) to reload the common data from the web.
I need a way to tell the 3 tab activities to refresh their views and rebuild their content from the newly downloaded data.
When the tab activities are first created they load the data from SharedPreferences like so:
public class TabA extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.taba);
SharedPreferences prefs = getSharedPreferences(this.getString(R.string.prefs_name), 0);
String usage_data = prefs.getString("common_data", "");
}
}
I thought about making a common method on each of the tab activities
like.... reloadViewData()
I thought maybe I could use the activity manager from the Main TabActivity to get the activity of the current tab like so:
String tabTag = tabHost.getCurrentTabTag();
Activity activity = getLocalActivityManager().getActivity(tabTag);
# then call the reloadData() method of the tab activity
activity.reloadViewData();
Unfortunately i cant get this approach to work, whilst activity is the correct instance its an Activity instead of a TabA,TabB or TabC
Maybe i've completely taken the wrong approach to the whole thing.
I have also read alot about not using Activities for tab content instead using views.
However I dont know what view to use to replace my <RelativeLayout /> as i cant use my R.layout.* as views.
If you change the common_data in the sharedPreferences you can also implement OnSharedPreferenceChangeListener in the Activites and check for key.equals("common_data").
You can extend Activity and make all your child activities to inherit from your extended activity, as this way you will implement the same code (service handling) in 1 activity(the parent).
Activity -> MyParentActivity -> MyChildActivity1
This way you will be able to cast all your child activities to MyParentActivity and run your custom method (you want to be implemented)
Related
Problem in short:
I have an MainActivity that holds BottomNavigationView and FrameLayout on top of it. BottomNavigationView has 5 tabs and when tab is clicked, I add some fragment on that FrameLayout. But, from some fragment, I need to open another fragment. From that another fragment, I need to open the other one. Every time when I need to show fragment, I notify MainActivity from fragment, that it needs to add the another one. Every fragment checks does its activity implement interface. And it is annoying. So, if I have 100 fragments, MainActivity implements too many interfaces. It leads to boilerplate code. So, how to properly navigate between fragments if you have a lot?
Problem in detail:
Please, read problem in short section first.
As I've said I have BottomNavigationView that has 5 tabs. Let's call the fragments that responsible for each tab as FragmentA, FragmentB, FragmentC, FragmentD, FragmentE. I really know, how to show these fragments when tab is clicked. I just replace/add these fragments in activity. But, wait, what if you wanna go from FragmentA to FragmentF? After that from FragmentF to FragmentG? This is how I handle this problem: from FragmentF or FragmentG I notify MainActivity that I wanna change the fragment. But how they communicate with MainActivity? For this, I have interfaces inside of each fragment. MainActivity implements those interfaces. And here is problem. MainActivity implements too many interfaces that leads to boilerplate code. So, what is the best way to navigate through Fragments? I don't even touch that I also need to handle back button presses :)
Here is how my code looks like:
MainActivity implementing interfaces to change fragments if necessary:
class MainActivity : AppCompatActivity(), DashboardFragment.OnFragmentInteractionListener,
PaymentFragment.BigCategoryChosenListener, PaymentSubcategoryFragment.ItemClickedListener, PayServiceFragment.OnPayServiceListener, ContactListFragment.ContactTapListener, P2PFragment.P2PNotifier
Here is my PaymentFragment's onAttach method for example:
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof BigCategoryChosenListener) {
listener = (BigCategoryChosenListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement BigCategoryChosenListener");
}
}
And using this listener I notify activity to change fragment. And in EACH fragment I should do so. I don't think that it is best practice. So, is it ok or there is a better way?
Ok What you need is something like this in activity where you would initialized on your BottomNavigationView.
bottomNavigationView.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_1://Handle menu click -
//Call Navigator helper to replace Fragment to Fragment A
break;
case R.id.menu_2:
//Call Navigator helper to replace Fragment to Fragment B
break;
case R.id.menu_3:
//Call Navigator helper to replace Fragment to Fragment C
break;
}
return true;
}
});
I have a tab view with two fragments. Those two fragments contain a recycler view with cards.
Each card in both fragments had a button.
Clicking on fragment 1's button should open the fragment 2 as a separate page and vice-versa.
I am struggling to find a method to implement this without making every too complex and tightly coupled.
This is fragment one with its own Adapter.
And this is fragment two:
Clicking on that SELECT DONOR button in Donees page should open donor fragment in a new page where the user will be able to assign a donor for the selected donee.
So I have two needs here
1) To start a fragment from a fragment
2) To Keep track from which Donee the new donor page was opened so that I can assign a donor for that specific donee.
I hope this is understandable.
so far I have tried LocalBroadcast and FragmentManager but its hard to keep track of what I'm doing with the code.
Can you guys suggest a better technique to achieve this ?
the easiest solution would probably be, starting a new activity, passing something like an ID, name or something to the intent on an Button click.
Context.startActivity(new Intent(Context, YourAssigneeActivity.class)
.putExtra("ID",id));
So I assume that you do not switch to the other tab when you click a button on one tab. Therefore the fragment should fill the whole screen.
With this assumption in mind you most likely have to switch the Activity. This can be dones easily with an Intent:
Intent intent = new Intent(getActivity(), ActivityB.class)
intent.putExtra("KEY", <your required data to transfer>);
getActivity().startActivityForResult(intent);
Note that when you use putExtra() don't forget that you need to implement Parcelable in those objects (explained here)
To get to know which item was clicked you can use the following pattern (pseudocode - I personally think it's really clean):
FragmentA implements YourAdapter.callback {
onItemClicked(<YourObject> item) {
<starting new activity as described above>
}
}
class YourAdapter extends RecyclerView.Adapter {
Callback mCallback;
YourAdapter(Context context, otherStuff) {
mCallback = (Callback) context;
}
interface Callback {
onItemClicked(<YourObject> item)
}
YourViewHolder implements View.OnClickListener {
onClick(View v) {
mCallback.onItemClicked(<YourObject> item)
}
}
}
Once you are in your Activity, you can set the Fragment in onCreate() of your Activity. In the Activity retrieve the data with getIntent() in the onCreate before creating the Fragment. Then you can put your data in the Fragment with setArguments(<Bundle>). In the Fragment in the onCreateview() retrieve it with getArguments().
I know this is kind of conmplicated. A better solution would be to just switch to an Activity and forget about the Fragment. This would remove one layer of complexity.
If you directly go from Fragment to Fragment you can ignore the Activity part but the rest should stay the same.
Hope this was the answer you were looking for!
Edit: Note that mCallback = (Callback) context is null if the Activity is not implementing Callback
Can anyone give a quick example of how to change the contents of an Activity action bar based on something that takes place in a fragment? My intent:
Normal menu items -> Something in the fragment is moved -> menu items change to save / discard buttons.
My first impulse is to setup Broadcast Receivers in both the activity and the fragment to cross talk, but I am not sure if this is correct.
Fragments can change menu in actionbar. For that you have to add necessary flag in fragment's oncreate() using method setHasOptionsMenu(true);
When your fragment is loaded you will get call at onCreateOptionsMenu(Menu menu, MenuInflater inflater) just like in an activity. Then do necessary changes to your menu.
Save you menu as global in fragment, and whenever you want to make a change, apply on it.
The following works for me. I have a custom class that implements ListView.MultiChoiceModeListener inside a Fragment:
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
// Choose the correct Action Bar menu to display
int menu = myCondition == true ? R.menu.my_default_menu : R.menu.my_menu_2;
// Configure to use the desired menu
mode.getMenu().clear();
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(menu);
}
Given how you detect 'something in the fragment has moved', extending ListView.MultiChoiceModeListener may not work for you, but hopefully this illustrates how to change the menu. The key is to get access to a ActionMode instance.
I think you want to use a contextual action mode. On the drag event, you will start a new ActionMode which can replace the contents of the action bar with menu items specific to what you want to allow the user to do. Once the user chooses an action, you finish the action mode and the action bar returns to its previous state.
Not sure if an ActionBar instance would help with the menu you but would surely be useful.. Here's a way to get about it
Try this to get the ActionBar from the FragmentActivity using the onAttach(Activity activity) method in the Fragment.
First of all make a global object of your FragmentActivity in the Fragment like this
public class YourFragment extends Fragment {
private YourFragmentActivity context;
}
Override this in the YourFragment class
#Override
public void onAttach(Activity activity){
context = (YourFragmentActivity)activity;
super.onAttach(activity);
}
Then in the OnCreate method in the YourFragment do this
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState){
...
android.support.v7.ActionBar actionBar = context.getSupportActionBar();
...
}
Is there a convenient way of showing the same Options menu options in multiple Activities?
Example: In my app, I display a TV Guide in one of three ways.
Seven day guide (TabActivity with 7 tabs)
All channels 'Now showing' (ListActivity)
All shows today by start time (Activity - could be changed easily to ListActivity)
For the Options menu in the TabActivity, the code is quite simple...
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
menu.clear();
inflater.inflate(R.menu.gv_options_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.view:
...
...
}
}
...but at the moment it seems I need to copy/paste it to the other two Activities which I don't like doing. If I change the Options menu code for one I'll need to do it for the other two also.
The only alternative I can think of is I have a 'helper' class (POJO) to which I could add a method and pass the context into to allow use of the getMenuInflator() method and another method I could pass the result of item.getItemId() into to process with the switch-case.
What is the normal way of having multiple Activities with the same Options menu?
Create a simple separate class with these two methods:
public class MyMenuHandler {
private Activity mActivity;
public MyMenuHandler(Activity activity) {
mActivity = activity;
}
public boolean onPrepareOptionsMenu(Menu menu) {
MenuInflater inflater = mActivity.getMenuInflater();
menu.clear();
inflater.inflate(R.menu.gv_options_menu, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.view:
...
}
}
}
In your activities override those callback methods and redirect the call to an instance of your MyMenuHandler class:
public class MyActivity1 extends TabActivity {
private MyMenuHandler mMenuHandler;
#Override
public void onCreate(Bundle savedInstanceState) {
...
mMenuHandler = new MyMenuHandler(this);
}
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
// you may also add here some items which are specific
// for one activity, not for the others
...
return mMenuHandler.onPrepareOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// handle selection of your specific items here,
// if none of them has been selected call mMenuHandler method
...
return mMenuHandler.onOptionsItemSelected(item);
}
}
This will let you hold in one place the code which respond to selection of your basic menu items, so there will be no need to worry about copy-pasting it to all activities which are to have the same menu.
One approach is to use inheritance with your Activities. Create a base Activity that implements the options menu methods and then each child Activity will gain that functionality. This is the recommended approach on the Android developer site:
Tip: If your application contains multiple activities and some of them provide the same Options Menu, consider creating an activity that implements nothing except the onCreateOptionsMenu() and onOptionsItemSelected() methods. Then extend this class for each activity that should share the same Options Menu. This way, you have to manage only one set of code for handling menu actions and each descendant class inherits the menu behaviors.
Unfortunately this won't work for you as you are not inheriting from Activity itself but differing subclasses of it, but that is the 'normal' way to do it.
You can encapsulate your action menu in a fragment. In this way you only need to add the fragment in the onCreate menu of your activity.
You need to call setHasOptionsMenu once the fragment is created.
To add the add fragment use a tag instead of a layout id.
i have TabActivity in android project which contains some tabs. In each tab i can open various activities, and after open it in a tab i want go back to previous activity in same tab, but default android behavior close my root tab activity. How i can realise behavior that i need?
There are a few ways of doing this. The first involves creating a custom GroupActivity that will keep track of the stack from the LocalActivityManager and then extending that class for each of your tabs. For that, check out this tutorial:
http://ericharlow.blogspot.com/2010/09/experience-multiple-android-activities.html
A simpler approach is to keep an array of your tab's subviews within your initial ActivityGroup class and then override the back button. Here's some sample code:
public void replaceContentView(String id, Intent newIntent) {
View view = getLocalActivityManager()
.startActivity(id, newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP))
.getDecorView();
viewList.add(view); // Add id to keep track of stack.
this.setContentView(view);
}
public void previousView() {
if(viewList.size() > 0) {
viewList.remove(viewList.size()-1);
if (viewList.size() > 0)
setContentView(viewList.get(viewList.size()-1));
else
initView();
}else {
finish();
}
}
The initView() class holds all of the inflating of the original activity's view. This way, you can call this method to regenerate the original activity if there are no more views in the array.