I have app where it's only a timer with a whole bunch of special methods in the main activity. This works great with no problems. Now, that I have it working, I'm trying to include it in a much more complicated application where I'm going to be inserting the timer in numerous places via fragments.
Should I include all of my special methods in my fragment activity? IE:
public class Timer_fragment extends android.support.v4.app.Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.timer_frag, container, false);
}
//SHOULD I insert my special methods here?
}
or should I include them in my main?
I'm asking because I've tried both ways, and the fragment activity way gave me errors in java, but putting them in my main gave me errors during runtime. Basically I have a timer that runs great as a standalone application, but I'm trying to convert it into a fragment, so that I can plug it into multiple places in my app.
Examples of "special methods":
I take a view in the layout and update it according to the new numbers inputted on the timer. These methods require the view to be inflated before they can be used.
Note: The errors at runtime I am experiencing are nullPointerExceptions.
The idea behind a Fragment is that it is a resuable section (ie fragment) of an Activity that can be used in other Activities, or even in the same Activity but with the addition or absence of other Fragments.
In the Gmail app for example, the list of all emails in an inbox is one Fragment, and the actual contents of an email is another Fragment. On a tablet in landscape mode, both are shown. A phone in portrait mode, however, will only show one of the two Fragments at once.
Thus each Fragment should encapsulate all functionality and UI components necessary to use that Fragment. This means that all of your "special" methods such as inflating the Fragment's view XML should happen within the Fragment.
The Activity (or Activities) that utilize the Fragment should only need to use a FragmentManager (or similar method) to add your Fragment to the Activity's layout. The parent Activity of a Fragment should also handle communication between Fragments and other components such as other Activities, Fragments, or Threads.
Related
I usually open a new fragment from an existing fragment or activity. However, with an adapter, which can be used in multiple fragments / activities, how do I open a particular fragment dynamically?
I usually use the following piece of code:
Navigation.findNavController(view).navigate(R.id.action_startFragment_to_destinationFragment);
after creating an action in the navgraph.
As navigation occurs among fragments, so normally you'd keep the fragment's the responsibility to do that.
As you told that adapter which can be used in multiple fragments, so it should be attached to a particular fragment at a time, which normally it's the fragment that instantiated it.
So, you can pass a listener interface to the adapter which is implemented by the fragment; where you can trigger its callback in the adapter whenever you want to navigate to another fragment in the nav graph.
This callback method will be executed at the fragment which already implements the listener, and you can normally use the traditional navigation code:
Navigation.findNavController(view).navigate(R.id.action_startFragment_to_destinationFragment);
This way your adapter can be reused, and every time a fragment wants to reuse it, it should implements the listener.
Note: Probably you can pass an int argument to the listener callback that pass in the row number in the adapter back to the fragment so that you might decide to navigate to some other fragment.
This way you can keep the navigation only through fragments.
I‘m just starting with android and i‘m wondering whether every activity need its own view or you can manage the ui in the activity itself ? Also it isn‘t pretty clear to me why the xml file belongs to the activity and not to the view
Any activity that has a UI needs a View. Activities handle a UI by displaying the View you set as the content view via setContentView. Activities have no direct drawing functions.
There are a few situations in which you may have an Activity with no View that just does some processing, but its generally a hack to get somewhere else- like an Activity that acts as a router.
Found the solution, see answer below
tl;dr - My View from the Fragment's onCreateView() is fine, but the result shown in the app is not. WTF?
I am new to Android. I am developing a single-activity Android app with some tabs managed by a TabLayout. The contents are Fragments displayed in a ViewPager. My activity_main.xml looks as follows:
<!-- SIMPLIFIED VERSION -->
<LinearLayout>
<android.support.design.widget.TabLayout/>
<android.support.v4.view.ViewPager/>
</LinearLayout>
I'm using a FragmentPagerAdapter which keeps the Fragments alive all the time (contrary to FragmentStatePagerAdapter), but destroys the View when the Fragment cannot be reached by the user from the current position. This means, that if the user is on the outer left tab, all tabs past the second tab from the left exist as a Fragment, but have no View. This seems counter-intuitive, as the user could just click on the tab he wants and reach ANY Fragment at all time, but I understand that the FragmentPagerAdapter only sees the ViewPager (which is navigated through swiping), not the TabLayout.
As the fragments Views get created, onCreateView() is called. When the View is detroyed, onDestroyView() is called. My Fragments are rather simple:
<!-- SIMPLIFIED VERSION -->
<LinearLayout>
<ScrollView>
<LinearLayout>
<!-- I want to dynamically add Views here -->
</LinearLayout>
<ScrollView>
<LinearLayout>
Now, I have a bunch ob objects in the background representing some data. When the Fragment gets a View (onCreateView()), I want to add custom Views for each data item into the Fragment. This should end up looking like a vertical list. I already instantiated the Views to be added in onCreate(). This seems logical to me, as onCreate() is only called once in my whole app.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// The fragment UI
_me = (LinearLayout) inflater.inflate(R.layout.fragment_log, container, false);
// LinearLayout inside the ScrollView
LinearLayout l = (LinearLayout) _me.findViewById(R.id.scrollableLinearLayout);
// Already instantiated CompoundViews
for(LogEntryCompound compound: _logEntryCompounds) {
l.addView(compound);
// My method to populate all TextViews, etc. from compound._data
compound.refreshUI();
}
return _me;
}
Since the view of the fragments gets destroyed, but not the fragment itself, this is done every time the user swipes too far from this fragment. I don't get this part completely (see my list of questions below), but it seems that when onCreateView() is called again later, the Compounds I already added are still there. Therefore I remove all Views in the Fragments' onDetroyView():
#Override
public void onDestroyView() {
super.onDestroyView();
LinearLayout l = (LinearLayout) _me.findViewById(R.id.scrollableLinearLayout);
l.removeAllViews();
}
The Compounds I display are sort of like Tiles, have some TextViews, ProgressBars, and are clickable. They directly represent a data object. All things they display come from this data directly (each Compound has a reference to his _data). I set all relevant UI elements within these Compounds by calling refreshUI():
public void refreshUI() {
_txtName.setText(_data.getName());
_txtDescr.setText(_data.getDescription());
_txtTopRight.setText(_data.getTimeString());
...
}
Now I have a really weird problem: All _data objects behave as they should, all the data is correct, even the Compounds' UI Elements have the correct .text set. The view I return in onCreateView is perfectly fine. But the View then shown in the app is not. Three out of four TextViews are fine, but one shows the same value for all 'Tiles' im displaying. The value displayed would be the correct value only for the LAST Tile.
Additionally, I have some more general questions (and I assume the more experienced readers will already be on their toes to tell me) about how things SHOULD be done:
1. Can I get the FragmentPagerAdapter to behave in a way that the views of the fragments are never destroyed? It's only three tabs and not a horrendeous amount of data. Recreating the View every time is surely inefficient?
2. How exactly does the FragmentPagerAdapter/ViewPager work? Why is it, that even though the View is apparently destroyed, it still contains the children I added before it got destroyed?
3. Should I add my Compounds anywhere else in code? Does the onCreateView() automagiacally restores its previous state? Or is onCreate() for instantiation and onCreateView() for adding to the UI correct?
After some late night debugging and code review I had a look at my XML file for the 'Tile'-View in question. For the TextView that behaved weirdly I found this:
android:textIsSelectable="true"
Removed it - everything works like a charm now.
Can't really explain this. Can only imagine that maybe the fact that the view is selectable, android messes up something and sees the TextViews from all Tiles as one and thus showing the same text across all.
So I have 2 fragments visible if the user uses a device with a big enough screen (like a Samsung Galaxy Tab).
Right now I am displaying a fragment list twice (same fragment twice). Inside the code of the fragment I use this to hide a progressbar.
ProgressBar pb = (ProgressBar)getActivity().findViewById(R.id.progress);
pb.setVisibility(View.GONE);
Problem is that it only works on one of the fragments. Both have the same id since its the same fragment?
Should I create 2 identical fragments or is it possible to find the "correct" progress-bar in the correct fragment?
In stead of finding the view (the ProgressBar) in the activity's view hierarchy, find it in the fragment's view hierarchy. So inside the fragment, do the following:
ProgressBar pb = (ProgressBar) getView().findViewById(R.id.progress);
Generally, you don't want to do lookups in the parent's view hierarchy, so above basically applies to all views in the fragment's layout.
I have a tabhost. One of the tab's activity is a ViewGroup. This viewgroup manages two different activities. I do this so I can navigate between activities within a tab. I add the activities like so:
if (videoViewLive == null)
videoViewLive = getLocalActivityManager().startActivity("VideoPlayerLive", new Intent(this,VideoPlayerLive.class).
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)).getDecorView();
videoViewLive.setVisibility(View.VISIBLE);
this.addContentView(videoViewLive, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,ViewGroup.LayoutParams.FILL_PARENT));
Each of my content view activities receives asynchronous notifications. What I would like to do is somehow remove the activity/content view that is not being used. So in essence, I load content view A, B dies, becomes null, or whatever, and vice versa. I want to do this because the way I am managing these views seems problematic. (errors when loading a view, loading the other view, then loading the first again, etc.)
Have you tried ViewGroup.removeAllViews()?
This is kind of tangential to the issue here, but why are you doing this with separate Activities?
This is exactly the kind of thing Fragments were designed for. There is actually a class called ViewPager included in the android support library (see also FragmentStatePagerAdapter) which allows the same kind of behavior via tabs (potentially in the ActionBar) or swiping. The adapter automagically handles the lifecycles of the Fragments as you navigate between them, all within the context of a single Activity, such that you can use the top-level Activity for routing events and maintaining overarching state if necessary.
I would try following approach:
//Add OnGlobalLayout Listener using ViewTreeObserver
View rootView = (android.R.id.content);
//Assuming you are managing these two activities inside the ViewGroup
Activity activityA = <someRef Value>;
Activity activityB = <someRef Value>;
rootView.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){
//try couple of things here
// 1. determine which activity has focus
// 2. you could also check position of View on screen to determine which one is active or on the top
if (activityA.hasWindowFocus())
{
//do some action --remove other content from ViewGroup
}
});