Say I have a fragment that has three buttons and I want to reuse it for at least three activities, but I want those buttons to do different things for each activity. For example, in ActivityA, I want button1 to open Google Maps while in ActivityB, button1 goes to the music player. Is this possible or even the right way?
Of course you can. Just create an interface for the Fragment, let's say FragmentCallback, with your desired callback method, onButtonClick() for instance. In the onAttached() of your Fragment, cast the Activity to your new interface and store it in a variable private FragmentCallback callback;. Each Activity using this Fragment must implement this callback interface. Then call the callbacks onButtonClick() method in your Fragments onButtonClick() method. That's it - a very common pattern.
Yes you can, but you have to add more logic to your fragments and add some interfaces for each activity.
I don't recommend to do that, maybe you could reuse your layouts.
Is this possible?
It definetely is. You could just check which Activity is hosting your Fragment instance:
private void button1OnClick(){
/* could also use instanceof, BUT: if you have something like ActivityC extends ActivityA
then instanceof would evaluate to true for both */
if(getActivity().getClass().equals(ActivityA.class)) {
// do stuff
} else if(getActivity().getClass().equals(ActivityB.class)) {
// do another stuff
}
}
Is this the right way?
(attention opinionated answer)
It depends. If you have a complex and unique layout/functionality, I'd use different Fragments. If you have a simple layout with some buttons that just need to act differently in different Activities it is a good idea to reuse an existing Fragment class.
Yes you can!
if(getActivity() instanceOf ActivityA) {
//do stuff related to ActivityA
} else if(getActivity() instanceOf ActivityB) {
//do stuff related to ActivityB
}
Your activities have different logic, you can define the button logic in each of them and share the views in this way. You can use a fragment to accomplish this however you can be more direct by sharing a partial layout.
Create a partial layout called three_buttons.xml
three_buttons.xml
<LinearLayout>
<BUtton android:text="button 1"/>
<BUtton android:text="button 2"/>
<BUtton android:text="button 3"/>
</LinearLayout>
activity_a.xml
<LinearLayout>
<TextView android:text="I am A"/>
<include
android:id="#+id/three_buttons"
layout="#layout/three_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
activity_b.xml
<LinearLayout>
<TextView android:text="I am B"/>
<include
android:id="#+id/three_buttons"
layout="#layout/three_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Related
Currently trying to add a layout (box.xml) to my main layout (activity_main.xml) from a different activity, but having trouble correctly doing this. Here is an example of what I'm trying to do:
(Note: removed params for simplicity)
activity_main.xml
<FrameLayout>
<ScrollView>
<LinearLayout>
<!--include layout="#layout/box"-->
</LinearLayout>
</ScrollView>
</FrameLayout>
box.xml
<TableLayout>
<TableRow>
<TextView/>
<ImageView/>
</TableRow>
<TableRow>
<TextView/>
<Button/>
</TableRow>
</TableLayout>
CreateNewBoxActivity.java
(Note: My MainActivity.java calls startActivity(new Intent(this, CreateNewBoxActivity.class)))
public class CreateNewBoxActivity extends Activity {
private ImageButton mFinish;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_newBox);
mFinish = (ImageButton) findViewById(R.id.btn_finish);
mFinish.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view)
{
//add box.xml where <include layout> is located in activity_main.xml
finish(); //close this activity and back to main activity to see
//added box layout
}
});
...
}
}
Thanks for any help on how to add this layout! Not sure the best way to tackle this.
You will want to use a LayoutInflater to inflate box.xml. Make sure you have an id for the LinearLayout that you want to add the box.xml to. Inflate that layout with something like
LinearLayout ll = (LinearLayout) findViewById(R.id.llId);
then you can use addView to add the box layout to your LinearLayout.
See this answer for a more detailed example
Edit
After further discussion, the OP wants to add certain elements from the layout in the second Activity to the layout used in the first Activity. This would be accomplished easier by using startActivityForResult() and passing back the data to the first Activity.
Then, in onActivityResult() in the first Activity the appropriate layout can be added to the original layout using the previously mentioned approach (inflating and calling addView()).
How to handle the data and what needs to be added depends completely on how this is handled by the dev. Data could be added to a model class from the second Activity then checked and added in onActivityResult() or this same method could check for Intent data passed back through setResult().
Intent Docs contains an example and the method needed to pass data back.
This answer has a brief example of using startActivityForResult() and passing back Intent data.
I'm trying to learn how to use a fragment as a worker for an android activity. I have the following simple xml layout for my main activity:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:id="#+id/update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Press me" />
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</LinearLayout>
I define my fragment using the following class definition:
public class UpdateTextFragment extends Fragment {
public static UpdateTextFragment newInstance() {
return new UpdateTextFragment();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public void startUpdateText() {
TextView textView = ((TextView) getActivity().findViewById(R.id.text));
textView.setText("I've been pressed!");
}
}
Then from my main activity I simply add the fragment and call the startUpdateText method of the fragment using the button's onClickListener , i.e.,
public void onClick(View arg0) {
UpdateTextFragment fragment = UpdateTextFragment.newInstance();
getFragmentManager().beginTransaction().add(fragment, "updateText").commit();
fragment.startUpdateText();
}
The code compiles and uploads to a tablet with no problems. I would expect it write the text "I've been pressed!" to the text view when the button is pressed, but the app just crashes with the standard "Unfortunately app has stopped working". I haven't implemented a class to catch this uncaught exception yet - I was hoping that it may be something obvious I'm missing or don't understand?
Thanx
Altough this is an old question I'd like to answer it as I was puzzled why your sample code didn't work.
The reason you get a NullPointerException is that you instantiate your Fragment and immediately call a method that requires to have the activity injected into the Fragment. The activity is injected by the FragmentManager / FragmentTransaction#commit method BUT this method does not evaluate the transaction immediately (as from the JavaDocs):
Schedules a commit of this transaction. The commit does
not happen immediately; it will be scheduled as work on the main thread
to be done the next time that thread is ready.
Which means
getFragmentManager().beginTransaction().add(fragment, "updateText").commit();
fragment.startUpdateText();
will yield the NPE in startUpdateText() (as the transaction was not executed yet!).
By adding the method call getFragmentManager().executePendingTransactions(); right after the commit the transaction will be performed immediately and the activity injected into the fragment. getActivity() in the Fragment now returns the Activity it was attached to and your sample works :)
As from the comments below your question: It is true that A Fragment is a re-usable 'UI-element' hosted inside an Activity. (#Stefan de Bruijn) but also that [...] a fragment is not required to be a part of the activity layout; you may also use a fragment without its own UI as an invisible worker for the activity. (as the official Android doc says).
So a Fragment is not necessarily a GUI component (i.e. the view in MVC) but also acts as a controller with its own lifetime/lifecycle.
I have the necessity to replace one starting fragment (I'll call it A) of an activity with two other fragments (B and C, in the "usual" list+viewer configuration). Currently I have a relative layout with two frame layouts acting as a placeholder for B and C:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<RadioGroup
android:id="#+id/radiogroup_navigation"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<!-- Some radiobuttons (not displayed for the sake of brevity) -->
</RadioGroup>
<FrameLayout
android:id="#+id/frame_list"
android:layout_width="100dp"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_below="#id/radiogroup_navigation">
</FrameLayout>
<FrameLayout
android:id="#+id/frame_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_below="#id/radiogroup_navigation"
android:layout_toRightOf="#id/frame_list">
</FrameLayout>
When I need to display A, I just hide frame_list and add A to frame_view, and when I need to display B and C I set frame_list visible again and add the two fragments to each frame, in the same fragment transaction.
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.remove(fragmentA);
t.add(R.id.frame_list, fragmentB);
t.add(R.id.frame_view, fragmentC);
t.addToBackStack(null);
t.commit();
In this way, when i press the back button, both C and B go away and I'm back to A fragment, but now frame_list is visible (and empty).
I am thinking to solve the problem in two possible ways:
overriding onBackPressed to hide the left frame if needed;
nesting B and C in another fragment;
But I also feel I'm probably looking at the problem in the wrong way, and maybe there's a cleaner design solution. Do you have any advice?
If I understand correctly, here is one solution:
Create two activities ActivityA and ActivityBC
Create another fragment with the radiogroup
Embed FragmentRadio into both ActivityA and ActivityBC
Have that fragment start new activities based on selection whilst finishing current activity
Make fields like this:
private static final String FRAGMENT_B_TAG = "fragmentB";
When you add the fragments, use the static Strings like tags:
t.add(R.id.frame_list, fragmentB, FRAGMENT_B_TAG);
t.add(R.id.frame_view, fragmentC, FRAGMENT_C_TAG);
In your activity, set up a listener, which will get triggered every time after you call addToBackStack(String). It will find out which fragment is currently visible and hide/show needed containers.
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
FragmentA fa = getSupportFragmentManager().findFragmentByTag(FRAGMENT_A_TAG);
FragmentB fb = getSupportFragmentManager().findFragmentByTag(FRAGMENT_B_TAG);
if (fa != null && fa.isVisible()) {
// Fragment A is visible, so hide the second container which is now empty
}
if (fb != null && fb.isVisible()) {
// Fragment B is visible, so show the second container
}
}
});
Notice that checking whether Fragment C is visible or not is not needed since when Fragment B is visible, Fragment C is always visible too.
This is an untested code, but I think it should work. Also, if you need any explanation, don't hesitate to ask.
Hope it helps.
Starting from this
<Button
android:onClick="onBtnClicked"
android:id="#+id/btn_edit_shared_preferences"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/btn_show_map"
android:layout_centerHorizontal="true"
android:text="Edit Shared Preferences" />
Here says that the only parameter send to the onClick callback function is the View object.
Well, I might need some "extra-parameter", like in this case: I have a function that handles the lauching of activities, so it would be
openActivity(View v){..}
On the other hand, in the layout I would like to have
:onclick="openActivity(activityClassName)"
What is the right way to do something similar to this?
You can use android:tag="YouActivity" along with your android:onClick="openActivity"
now you can have Activity name in your openActivity() like this:
void openActivity(View v)
{
String activityName = v.getTag().toString();
Class<?> aClass = null;
try {
aClass = Class.forName("your.package."+activityName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
startActivity(new Intent(this, aClass));
}
Well, the View object only parameter which the onClick event gets but you can get more information through the Context object.
A single button can correspond to a single method. There is no way to pass through extra paramaters. For example, in your case, you want the button to launch an activity. One way to do that is to write a method just for launching that activity.
<Button
android:onClick="launchPreferencesActivity"
android:id="#+id/btn_edit_shared_preferences"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/btn_show_map"
android:layout_centerHorizontal="true"
android:text="Edit Shared Preferences" />
Then in your Activity:
public void launchPreferencesActivity(View v){
//start Activity
}
Different buttons can correspond to different methods to handle the click event. Or if you want, you can use the View parameter to distinguish what view is calling the method and handle it that way.
If there is some other factor going on that might change the behavior of the button, you can always change the onClickListener in code. But generally, I would think the above approach works better for what your are trying to do.
EDIT:
So after the comments below, I revisted and realized what was hanging me up.
Imagine my client list and client details activity be started by :
public class ClientsMainActivity extends FragmentActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//StudioTabOpenHelper db;
setContentView(R.layout.main_client_activity);
}
}
So this works great, starts up my main_client_Activity (defined in a layout below, and i call this activity when a button on my main screen is clicked):
Intent intent = new Intent(getActivity(), ClientsMainActivity.class);
startActivity(intent);
Easy the issue is, the ClientsMainActivity does not call a OnCreateView or anything, just sets the layout to the layout that defines my Fragment, and my ListFragment. This is fine cause I am not trying to pass anything into the ClientsMainActivity, but if I have a hypothetical activity like:
SessionMainsActivity that is called when they click on the session edit of a client, then I would not be calling the SessionsMainActivity the same way (starts activity that just sets to alayout), i would want that layout set as it defines how my fragments are split up. But I would also want to pass in data to that and then to the subsequent fragments (like which session they clicked on to be editing/mucking with.
So I wonder if the above makes sense, I am sure its a simple thing I just cannot wrap my brain around. I have no issues calling FragmentActivities from other fragments, they take up the whole screen but it works. So I guess the big issue is that ClientsMainActivity is from some example I found online for doing recipes that shows you how to make multiple fragments to a screen. The thing that gets me all that FragmentActivity does is sets the content view, to a layout that does all the work it seems, so that's why I cannot figure out how I would code it to do the same thing but let me pass in values to the fragments the layout defines etc...
END EDIT
So I am using this nice little tutorial here:
http://developer.android.com/guide/topics/fundamentals/fragments.html
It has gotten me a long way and utilizing what they say to do for the main activity, and the fragment_layout.xml, I got a nice client list on the left (Thats a listfragment) and a details fragment on the right.
Then i added the ability to edit session information on a client (or edit client details) both of which were full screen fragments. This worked great.
Now I decided my Session edit ui would best be served splitting the information up into two panes again.
This is not working as I thought, like I said I have a main_Activity that does this in the onCreate:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_client_activity);
}
with the main_client_activity.xml being defined in two layouts but the one for landscape tablets is here:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="com.viciousbytes.studiotab.subactivities.ClientListView"
android:id="#+id/client_list" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent" />
<FrameLayout android:id="#+id/client_details" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent"
android:background="?android:attr/detailsElementBackground"/>
</LinearLayout>
This all works great! In which case I handled everything else as a full screen activity that started its own fragment:
EditSessionActivity
EditClientActiivyt both of which use getSupportFragmentManager().beginTransaction and I could pass information into it from the .newInstance call.
I had my session_edit.xml layout defined with buttons, textviews etc..and that was working great. Thats what i loaded in my SessionEdit fragment "launched" by my EditSessionActivity But now since I want to split it apart I ran into a snag. Above I defined a client_list and a client_details id, are these placeholders on my screen? do I reference those when I wanna replace whats there with totally different fragments?
or do i build another fragment layout called something like fragment_session_layout which defines something like:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="com.viciousbytes.studiotab.subactivities.SessionEdit"
android:id="#+id/session_edit" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent" />
<FrameLayout android:id="#+id/invoice_details" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent"
android:background="?android:attr/detailsElementBackground" />
</LinearLayout>
Sorry don't know what to title this on the tip of my tongue of what I am asking, basically how to get two panes of fragments twice over. THe demo online shows how to do one (and a simple ListFragment at that).
I have done all the above and I just cannot figure out how to pass into the fragment the data I need, I was using this in my EditSessionActivity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int clientID = getIntent().getIntExtra(THE_SELECTED_CLIENT, -1);
int sessionID = getIntent().getIntExtra(SELECTED_SESSION,-1);
SessionEdit edits = SessionEdit.newInstance(this.getBaseContext(), false, clientID, sessionID);
mUIListener = (OnUpdateUI)edits;
getSupportFragmentManager().beginTransaction().add(android.R.id.content, edits).commit();
}
that worked, but to try to adhere to the earlier fragment example, i assumed my EditSessionActivity was sorta like making another MainActivity (cause it has two panels in it like the main one). so I recoded the onCreate in EditSessionActivity with this:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.edit_session_fullview);
}
Which after fighting in my EditSession fragment dealing with the onCreateView, I got this to finally work but couldn't pass in the data cause there is no instantiation of the object using like the previous onCreate which had the edits=SessionEdit.newInstance(...)
So is it good practice to be doing the other fragment layout that has two pains in it and starting that up when the right action is triggered. Or is one supposed to replace the two already created fragments?? from my main_client_activity.xml somehow?
I assume editing clients and editing sessions are two distinct activities. When you switch from "editing clients" to "editing sessions" mode, both the "list" and "details" panes would change?
I would go with two layout files, instead of trying to reuse the same layout and reload fragments in it.
If you tried to reuse the same layout, you would have to:
Change #+id/invoice_details to something like #+id/right_pane. Otherwise it would look confusing to load something related to sessions into "invoice_details" placeholder.
replace fragment definition with another FrameLayout and load either ClientListView or SessionListView (or however it's called) fragment there at runtime.
This would add more complexity than having another layout xml file in my opinion.
So
Take your existing code that works with client list and client details
Duplicate all involved parts, and change what needs to be changed so it's now session list and session details
Remove duplication where it's easy to do (common functions go to utility classes, common layout elements to layout includes). Leave the things that are hard to de-duplicate as-is.
Re-evaluate later, when you have more fragments, more layouts and more experience.
UPDATE, about two different approaches fragments can be embedded in activity
As the Android documentation states, there are two main ways you can get a fragment to show up in your activity:
declare the fragment in layout's XML file (<fragment class=.... />)
put a placeholder FrameLayout in layout's XML file and load fragment at runtime
First approach is fine when fragment doesn't need to receive any arguments. Like, for example, if the logic to retrieve single and only list of clients is hardcoded in fragment's code.
Second approach lets you pass arguments to the fragment, and therefore is appropriate for "details drilldown" type of fragments.
From updated question I understand that,
each client has a separate list of sessions
the components in play are: EditSessionActivity that hosts two fragments, one for displaying list of sessions, another for displaying session details
If that's correct, then indeed you'd need to load both fragments programmatically because both needs parameters to be passed to. So your layout would have two FrameLayouts. The EditSessionActivity would start with getting some parameters from intent ("which list of sessions are we working with?"), and load "list of sessions" fragment with these parameters. When user selects list item in that fragment, the other fragment would be loaded with session details.