Using fragments as workers for activity - android

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.

Related

Android Fragment in Layout Losing Listener

An interesting issue here. Writing an app in Kotlin (which is awesome btw) and due to customer design restraints we've had to implement a custom navigation drawer(wish we could use the native navigation view with app:menu but we can't).
Embedded fragment
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="false">
<fragment
android:name="com.redacted.app.nav.NavDrawerFragment"
android:id="#+id/nav_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.design.widget.NavigationView>
// our callback
interface NavDrawerListener {
enum class CurrentState {
StateOne,
StateTwo
}
fun onStateOneClicked()
fun onStateTwoClicked()
}
In our case, we've created a Fragment call NavDrawerFragment that contains a RecyclerView with items and a callback interface that get hooked into in the fragment's onAttach(context: Context) method that lets the activity know the item that was clicked and any additional payloads needed to start the next activity. All works as expected, with the new activities using the same base layout and the fragments listener being implemented by the activity until... the back button is pressed. It appears that pressing back on Activity B calls onDetach on the fragment and when ActivityA resumes it the fragment instance's onAttach method never gets called again.
Am I missing something about fragments being embedded into a layout or is this behavior expected? All I need at the end of the day is for ActivityA's implementation of NavDrawerListener to be valid onResume().
I just went through something similar, as in weird fragment behavior. I resolved all the weird issues and errors by calling
SupportFragmentManager
The fragment itself was then still acting weird till I realized that it imported the regular fragment. Once I changed it to v4 fragment import all the weird quirks went away.
This might not help but hope it does.
Ok it turns out I quite misunderstood exactly how variables/values behave inside a companion object in kotlin. They are definitely static in nature. So what it seems actually happened was that when activity B finished and brought activity A back to the foreground that the detach method was setting the global companion object listener value to null so activity A could not access it.
Final Answer: make the listener interface variable a property of the instance vs a companion object member.
Thanks again for all the help and insight!

How to replace a fragment and change TextView in the new one in the same procedure?

Hello you!
I am not a native english speaker, so hope you understand me anyway.
In my application i have a activity with a RelativeLayout wich i use as Fragment-container.
<FrameLayout 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" >
<RelativeLayout
android:id="#+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white" />
</FrameLayout>
At the time i have two different Fragments, one will be shown at Startup and die other one will be called by click on a TextView in the first one. Both Fragments have its own class and the first one has a interface to the main activity to perform the replacement of the Fragment after onClick.
And now there is my Problem....
public void onCallForAGB() {
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, new LoginReaderFragment(), "agbreader")
.addToBackStack(null)
.commit();
LoginReaderFragment fragment = (LoginReaderFragment) getFragmentManager()
.findFragmentById(R.id.fragmentContainer);
if (fragment != null && fragment.isInLayout()) {
fragment.setText("Gruß an die Welt!");
} else {
Toast.makeText(mContext, "Fail", Toast.LENGTH_LONG).show();
}
}
just for explaining:
the container holds a Fragment named LoginLoginFrament. The user of my app can Login or click on a link (TextView) to show the AGB (dont know how to translate, maybe "Therms for use and Law dependencies" ??). Via the interfaceonCallForAGBwill be executed and replace the Fragment with a Fragment calledLoginReaderFragment` wich is a Fragment with a headline (TextView) and a textfield (TextView) to show filecontents. The function setText(string) should set the text after the Fragment was created.
But after Fragmenttransaction.commit() it seems the change will not be executed immediately. It seems it will be executed when leaving the whole procedure. So i can't access the new fragments Views (to change text) without the complete perfomed replace by commit.
So here is my question: How can i force the FragmentTransaction to be executed after commit() ? Or is there a workaround, so that i can change the headline and text of the new Fragment right after changing from the first to the second Fragment?
Here is the Errorreport (LogCat) which is why I have this assumption
05-29 20:59:18.748: E/AndroidRuntime(14334):
java.lang.ClassCastException:
de.example.myapp.fragments.LoginLoginFragment cannot be cast to
de.example.myapp.fragments.LoginReaderFragment
I think LoginReaderFragment fragment = .... create this error because the old Fragment (LoginLoginFragmet) is still active.
Hope you can help me.
Greets !
Do not try to set the Fragments UI data from the Activity itself after creating it.
Instead, give the fragment the information, which should be shown after the fragment is visible.
The best way to do this is to use the getInstance() pattern. see here.
You need to call the setText function from onViewCreated() in the fragment itself. this grants you that the data will be set, after the view is availible.

Using Fragments before they are attached

I am creating a master/detail type application. The Master view is a MasterFragment that shows a list of Master items, while the Detail view is a DetailsFragment that shows a list of Detail items.
When a user clicks a Master item in the list, I create a new DetailsFragment and show it using a transaction.
The details shown in a DetailFragment take some time to load (seconds) so I want to load them in a background thread and show the list once the loading is finished.
I now want to give the user the option to long-click a Master item, which (instead of opening it immediately and letting him wait) will create the DetailsFragment in the background (not visible yet), allowing him to browse the MasterFragment while it's loading. A navigation item is added to the Navigation Drawer so he can navigate to the DetailsFragment after some time when it has finished loading.
Think of it like using a web browser on very slow internet - instead of opening a page and waiting for it to load it is much nicer to open a page in a new tab in the background, browsing the current page some more while it loads, and then going back to the new tab when you think it must be finished loading. That's what I want to do in my app as well except with Fragments.
Now I learned that with creating Fragments it's important to use a static factory method that creates the Fragment, adds any objects as arguments to a Bundle, and then leave only an empty constructor.
public class DetailsFragment : Fragment
{
public DetailsFragment()
{
// Leave empty
}
public static DetailsFragment create(int masterId)
{
DetailsFragment f = new DetailsFragment();
Bundle args = new Bundle();
bundle.putInt("MasterId", masterId);
f.setArguments(args);
return f;
}
#Override
private void onCreate(Bundle bundle)
{
super.onCreate(bundle);
// Get master ID
int masterId = getArguments().getInt("MasterId");
// Load details in background thread
load(masterId);
}
#Background
private void load(int masterId)
{
//... (loading takes a few seconds...)
loadFinished();
}
#UiThread
private void loadFinished()
{
// update view...
}
}
(Note: I am using Android Annotations so that the 'load' method (with the #Background annotation) is run in the background. Just pretend I start it using a runner or AsyncTask or whatever.)
There is a problem here however: onCreate is not called until the Fragment is 'called upon', in other words there is no loading being done until the user opens the details fragment. I have tried onAttach instead of onCreate but the same thing happens. It seems onAttach is the first method called in the lifecycle and that is already too late.
I want the loading to start immediately, even if the Fragment is not shown yet (it may never be shown if the user doesn't navigate to it anymore).
How can I implement this behavior?
This is how I preload a fragment. Not sure it is the best way or the android way but it's the way I figured out how to do it.
In my layout xml I have the following:
<FrameLayout
android:id="#+id/master_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible" />
<FrameLayout
android:id="#+id/details_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"/>
Notice that the "details_frame" is invisible. Then when you want to preload the fragment , you replace the invisible details_frame with the fragment and it will remain invisible:
getSupportFragmentManager().beginTransaction()
.replace(R.id.details_frame, <DetailsFragment>, <FragmentName>)
.commit();
Then once you want to display it you change the visibility to visible instead of invisible.
findViewById(R.id.details_frame).setVisibilility(View.VISIBLE);

Trying to Create a Button to Open a Fragment (Android)

So I basically have a button in 'DemosFragment' and when I click it, I want it to open another fragment (SettingsFragment), I understand now that I need an activity to fix this issue, as the button currently has an onClick method using intent/startActivity, so how would I go about creating an activity that just holds my fragment? I know that may sound weird they way I wrote it, I just started Android development, but basically I have a fragment and because I want a fragment to have a button to open another fragment, I figure I need an activity for the fragment I am trying to open, so how do I create that activity and what do I need to put in it? Thanks.
You need an activity with the following code:
public class ShowFragmentActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_show_fragment);
}
}
You also have to create a layout xml file called activity_show_fragment.xml in your res/layout folder:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="com.example.yourFragmentsClassName"
android:id="#+id/fragment_id"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
This should work for just displaying your fragment.
To launch the activity, paste this code in your button's onClick method:
Intent i = new Intent(this, ShowFragmentActivity.class);
startActivity(i);
It's always a good decision to look at the official docs: http://developer.android.com/reference/android/app/Fragment.html.
Hope that helps!
Wow! Your question requires a long answer, however is a good practice (and madatory too) that Fragments cannot communicates between each others, but they can be hosted by an Activity; in that case an Activity can manage the communication flow between them (fragments) and can be developed in several ways, Bundle, Intent and the Handler. Have a look to the ufficial Android documentation here:
http://developer.android.com/training/basics/fragments/index.html
The android docs section on building a flexible UI is a good example of how to start/load a Fragment from an Activity. In the example you will see that a FrameLayout in the Activity XML is used a the fragment container. This will be the View in which all of your fragments are displayed.
When you load your fragment with a FragmentTransaction the contents of your fragments layout will be displayed in the container View. In the above referenced example this takes place with SupportFragmentManager a class included with the android support library, for facilitating fragment transactions in earlier version of the operating system. SupportFramgnetManager requires that you extend FramentActivity and not just Activity. If you're not worried about backwards compatibility and are extending activity, not fragment activity, you can simply use getFragmentManager() instead.
getFragmentManager().beginTransaction()
.add(R.id.fragment_container, firstFragment).commit();
After the initial add transaction you can switch between fragments using the replace method for your fragment transaction. Replace does exactly what it sounds like, it swaps one fragment for another. To accomplish this from within your firstframgnet use
SecondFragment secondFragment = new SecondFragment();
getActivity().getFragmentManager().beginTransaction()
.replace(R.id.fragment_container, secondFragment).commit();
Notice that from within the fragment I used getActivity(). This allows you to reference the context of the host activity to access the fragment manager. When you are within the activity you do not need to use getactivity because the fragment manager is already accessible from that context.

Simple activity switch not working. No Errors

I have a basic calculator app I'm making. Two activities, the main one and ResultView.
I've made it where I click a button on activity A to go to activity B. The log says activity B is started and "displayed" successfully, the title for the new activity loads, but the body does NOT show. I added a simple Text view with static text.. see the result.xml at the bottom. I also tried inserting information programmatically, but that didn't do.
When I debug the program, I tried putting breakpoints as the activity is called with startActivity() as well as on the first line of the onCreate method within the ResultView class (my activity "B") but the program never hits the second breakpoint. In fact, it looks as if Looper.class is called in the end.
This bit of code is placed in the button handler on acitivity A:
i.putExtra("test1",34);
i.putExtra("test2",35);
this.startActivity(i);
This in the onCreate function in activity B:
public void OnCreate(Bundle
savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.result);
}
The activity is in the manifest, within the "application" tag:
<activity
android:name="ResultView"></activity>
If I didn't supply enough info, let me know.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/llParent"
android:orientation="vertical"
android:padding="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:text="HELLO WORLD"
/> </LinearLayout>
If more info is needed, let me know...in short, "HELLO WORLD" does not display at all.
It's not OnCreate, it's onCreate (lowercase o). Otherwise the method won't be overriden. The #override annotation has no effect if it's omitted, it's just for readability for the programmer.
Are you sure that the public void line or the line before that contains #Override? If not, you're not overriding the OnCreate method. The code should read
#Override public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.result);
}
EDIT
Of course the "O" must not be a capital "O"...

Categories

Resources