android Fragments onFocusChanges - android

I have some code which I run in normal activities in method onFocusChanges, it is important to run it just there as the code requires the activity to be loaded first as it get images width and heights from the view:
in MainActivity
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
SizeModifier sizeModifier = SizeModifier.getInstance(this);
sizeModifier.adjust
}
so all this is working just fine, the problem is this main view contains a fragment changes according to button press like that
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.main_view_fragment,fragment).commit();
the fragment of course has activity and xml, I want now when the fragment open to run the previous code when focus changes but the problem is fragments does not have onFocusChange, I tried all onStart onResume onCreateActivity
but non seems to work as I want, of course the method is called but all images widths are returned 0,
so is there away to be sure that images will return correct width or some alternative to onFocusChanges

Ok, finally I found what could do the purpose,
simply for fragments we add this simple code to the onCreateView after inflating the view like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rootView = inflater .inflate(R.layout.activity_courses_fragment, container, false);
// >>>> this is the thread which will run when every thing is ready
rootView.post(new Runnable() {
#Override
public void run() {
// >>> this method now can get width with no problems
SizeModifier.getInstance(getContext()).adjust(rootView);
}
});
return rootView;
}
special thanks to #Staffan for this idea as solution for another question

Related

Android Fragment display a loading while rendering layout

Let's say I've got an Activity with only a FrameLayout (I've also try with the new FragmentContainerView) so I will be loading Fragments on it. Let's assume I've got two Fragments: fragA and fragB, I will first load fragA and with a button, present in that fragment, replace it with fragB.
I've define a function on my Activity so I can load a new Fragment on the container:
public void loadFragment(Fragment fragment) {
FragmentTransaction fragTrans = getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragment)
.addToBackStack(null);
fragTrans.commit();
}
And I will call this from fragA to go to fragB. This far so good, everything works fine. Actually the problem I'm facing is not about functioning, is that I don't like the loading time of fragB, it takes about 2sec, and I'd like to display a loading to the user, but I'm not able to achieve it.
The first thing I've try is adding a gone ProgressBar to the activity layout, where the container is. Then my loadFragment function will first set this progressBar visible and then perform the fragment transaction and each fragment will hide (gone) this View before exiting the onCreateView method. But the problem is that the user never sees this ProgressBar, it's like the main UI is freezed while rendering the layout of the fragB and therefore does not update the UI making this progressBar usesless. I even see some frame skipped in the LogCat about 50 frames.
Both fragments have no logic just the onCreateView implemented where the layout is inflated. I can notice that if I change the layout of fragB to a simpler one it loads instantly, so I'm guessing the issue is related to the time it takes to render the layout. This heavy layout is a really big form with lots of TextInputLayout with TextInputEditText and MaterialSpinner (from GitHub).
I know I maybe can simplify the layout, but I'd like to know how can I display a loading while rendering. For example, I've seen some apps load some kind of dummy-view while loading and then replace it with real data.
One thing I've try is to load a dummy layout with a ProgressBar in the middle and in the onCreateView method post a Handler to inflate a real layout on the same container in the background, like this:
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_dummy, container, false);
Handler mHandler = new Handler();
mHandler.post(new Runnable() {
#Override
public void run() {
container.removeAllViews();
View realView = inflater.inflate(R.layout.fragment_real, container);
}
});
}
This kinda work, as the viewing experience is nice, but when navigating back, the layout of fragB remains visible as background of the other fragments. I have not test it but maybe I can call container.removeAllViews(); before exiting the fragment and it will work, but still seems like a workaround rather than a solution, to me.
And other thing I've not try because maybe is an over-kill is to have an intermediate or loading fragment and load it always before the real fragment, and I will pass an Intent Extra to it so I can tell what's the real fragment I want to display. This intermediate fragment will not be added to the backstack.
How do you solve this kind of problems?
Well it seems that AsyncLayoutInflater is the way to go!
Here's how I use it:
private ViewGroup fragmentContainer;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View dummyView = inflater.inflate(R.layout.fragment_dummy_loading, container, false);
fragmentContainer = container;
AsyncLayoutInflater asyncLayoutInflater = new AsyncLayoutInflater(getContext());
asyncLayoutInflater.inflate(R.layout.fragment_real_layout, container, new AsyncLayoutInflater.OnInflateFinishedListener() {
#Override
public void onInflateFinished(#NonNull View view, int resid, #Nullable ViewGroup parent) {
fragmentContainer.removeAllViews();
fragmentContainer.addView(view);
}
});
return dummyView;
}
#Override
public void onStop() {
super.onStop();
fragmentContainer.removeAllViews();
}
Up there, fragment_dummy_loading is a layout with a ProgressBar and fragment_real_layout is the real heavy layout.
There's just one thing I don't get... and that's how can I bind Objects to the XML widgets (without using Android Data Binding, if possible) when the AsyncLayoutInflater fallsback to infalte in the UI thread.
According to the docs:
If the layout that is trying to be inflated cannot be constructed asynchronously for whatever reason, AsyncLayoutInflater will automatically fall back to inflating on the UI thread.
And I check the source of AsyncLayoutInflater and I can tell that the method onInflateFinished is called no matter if the inflation was in the background or in the main thread
I think you have to use AsycTask when your new fragment loading
1- Load the new fragment
2 -Start Async task
3 - Initialize Progressdialog in constructor of AsyncTask where you want to load the data
4 - Show Progressdialog in onPreExecute() in your AsyncTask with dialog.show()
5 - Dismiss the Progressdialog in onPostExecute() in your AsyncTask with dialog.dismiss()
6- Update the UI / set the new data to your adapter etc
Check here
Show a loading spinner while a fragment is loading

ListView is not updating in a fragment

I have 5 fragments in an activity. And they are being showed using tabs/viewpager. Suppose they are nameD as ONE, TWO, THREE, FOUR and FIVE. I am displaying data in all fragments as ListView. I am inflating data in ListView from database cursor. The data is updated normally when i perform an add or delete in the same fragment. The problem occurs when i send/move a data from one fragment to another.
EXAMPLE OF PROBLEM:
I send/move data from fragment ONE to fragment TWO. Then I tap on fragment TWO to view data. It is not there. The data is shown when I tap on fragment FOUR or fragment FIVE and then come back to fragment ONE. Or if the app is restarted or any other Activity comes in front and goes back.
The data is not shown by clicking the adjacent tabs or swapping to adjacent tabs. And then coming back to the tab from which data was moved
I am sure someone of you must have an idea whats happening here and tell me how to solve the issue.
onCreateView
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.frag_dramas, container, false);
setHasOptionsMenu(true);
list = (ListView) view.findViewById(R.id.mylist);
return view;
}
onResume
#Override
public void onResume() {
super.onResume();
getListView();
}
onViewCreated
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
add();
}
});
}
There are other methods in the fragment too but they are not relevant to the issue. I Think these three methods are relevant. And i have no onCreate() method in the fragment..
If Fragment is already in the memory you should use BroadcastReceiver to notify other fragments whenever any data is added/removed/updated.
You can try EventBus as well.
https://github.com/greenrobot/EventBus

How to prevent the reload of a webView in a fragment when another fragment in the top was closed

I'm making an app with webviews.
I just have a single activity containing
a FrameLayout to dynamically add fragments.
Every fragment contains a webView.
Everything works fine, but when I remove a fragment
from the stack, the webview of the fragment in the
top of the stack is reloaded so the content inside the
script of the html is called again.
The restoreState() method is not working.
This is the onCreateView of the fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.wv_container, container, false);
webView = (WebView)view.findViewById(R.id.webviewcontainer);
if(webViewBundle == null)
webView.loadUrl("file:///android_asset/www/main.html");
else
webView.restoreState(webViewBundle);
return view;
}
This is the onPause() method:
#Override
public void onPause() {
super.onPause();
webViewBundle = new Bundle();
webViewContent.saveState(webViewBundle);
}
How should I prevent the page reloading for my case?
I don't want to hack the html with flags or something like that.
Thanks in advance!
The saveState method in your onPause returns a WebBackForwardList, which contains a list of WebHistory items. These are basically the titles, urls and favicons from recently visited pages. It does not cache the contents of the downloaded url.
Try looking at the method saveWebArchive instead.

Stop fragment from being recreated after resume?

I am using several fragments to be dynamically added into activity. Everything works fine, when I press back-button, the fragments go to backstack. And when I resume it, it appears. But everytime on Resume, it is recreating the fragment and call onCreateView. I know it is a normal behavior of the fragment lifecycle.
This is my onCreateView:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(
R.layout.competitive_programming_exercise, container, false);
return rootView;
}
I want to stop those fragments from recreating. I tried with onSavedInstanstate but nothing is working. How can I accomplish that?
In the Activity's onCreateView set the savedInstanceState to null before calling the super method. You could also remove only the keys "android:viewHierarchyState" and "android:fragments" from the savedInstanceState bundle. Here is code for the simple solution, nulling the state:
#Override
public void onCreate(Bundle savedInstanceState)
{
savedInstanceState = null;
super.onCreate(savedInstanceState);
...
}
Iam using 5 fragments and working for me good as I was facing the same issue before..
public class MyFragmentView1 extends Fragment {
View v;
#Override
public View onCreateView(LayoutInflater inflater,
#Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
if (v == null)
v = inflater.inflate(R.layout.my_fragment_view_layout,
container, false
);
return v;
}
}
I put the view variable inside class and inflating it as new only if the view instance is null or otherwise use the one created before
You can't stop the fragment from being recreated, unfortunately. The best you can do is to remove the fragment in a transaction, after it has been restored but before it gets displayed.
If you know you are going to remove the fragment immediately you can reduce the performance hit of restoring the fragment by simplifying methods such as onCreateView() to return a dummy view, rather than inflating the whole view hierarchy again.
Unfortunately the tricky part is finding the best place to commit this transaction. According to this article there are not many safe places. Perhaps you can try inside FragmentActivity.onResumeFragments() or possibly Fragment.onResume().

Handling orientation changes with Fragments

I'm currently testing my app with a multipane Fragment-ised view using the HC compatibility package, and having a lot of difficultly handling orientation changes.
My Host activity has 2 panes in landscape (menuFrame and contentFrame), and only menuFrame in portrait, to which appropriate fragments are loaded. If I have something in both panes, but then change the orientation to portrait I get a NPE as it tries to load views in the fragment which would be in the (non-existent) contentFrame. Using the setRetainState() method in the content fragment didn't work. How can I sort this out to prevent the system loading a fragment that won't be shown?
Many thanks!
It seems that the onCreateViewMethod was causing issues; it must return null if the container is null:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (container == null) // must put this in
return null;
return inflater.inflate(R.layout.<layout>, container, false);
}
Probably not the ideal answer but if you have contentFrame for portrait and in your activity only load up the menuFrame when the savedInstanceState is null then your content frame fragments will be shown on an orientation change.
Not ideal though as then if you hit the back button (as many times as necessary) then you'll never see the menu fragment as it wasn't loaded into contentFrame.
It is a shame that the FragmentLayout API demos doesn't preserve the right fragment state across an orientation change. Regardless, having thought about this problem a fair bit, and tried out various things, I'm not sure that there is a straightforward answer. The best answer that I have come up with so far (not tested) is to have the same layout in portrait and landscape but hide the menuFrame when there is something in the detailsFrame. Similarly show it, and hide frameLayout when the latter is empty.
Create new Instance only for First Time.
This does the trick:
Create a new Instance of Fragment when the
activity start for the first time else reuse the old fragment.
How can you do this?
FragmentManager is the key
Here is the code snippet:
if(savedInstanceState==null) {
userFragment = UserNameFragment.newInstance();
fragmentManager.beginTransaction().add(R.id.profile, userFragment, "TAG").commit();
}
else {
userFragment = fragmentManager.findFragmentByTag("TAG");
}
Save data on the fragment side
If your fragment has EditText, TextViews or any other class variables
which you want to save while orientation change. Save it
onSaveInstanceState() and Retrieve them in onCreateView() method
Here is the code snippet:
// Saving State
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("USER_NAME", username.getText().toString());
outState.putString("PASSWORD", password.getText().toString());
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.user_name_fragment, parent, false);
username = (EditText) view.findViewById(R.id.username);
password = (EditText) view.findViewById(R.id.password);
// Retriving value
if (savedInstanceState != null) {
username.setText(savedInstanceState.getString("USER_NAME"));
password.setText(savedInstanceState.getString("PASSWORD"));
}
return view;
}
You can see the full working code HERE

Categories

Resources