View overlap after updating fragment with Asynctask - android

What i pretend is to display an indeterminate progress bar (for the moment is only an dialog for test purposes) while my second fragment is getting data and constructing the view without freezing the UI.
What i've tried:
AsyncTask
Loader
Loader with AsyncTaskLoader
Adapter (not tried but maybe its an option but i don't know how to
use an adapter with a custom layout)
With the asynctask approach almost everything is right, but onPostExecute I've to update the fragment to do this i created an interface and in the main activity i remove the view and add the new one, but with this i created another problem my back stack is messed up so i'am out of options.
A diagram to help understand how it works:
My fragment 2:
public class FragTopics extends Fragment {
Object course;
ManSession session;
String courseId;
Long topicId;
String courseName;
private LinearLayout mainLayout;
private ScrollView contentScrollable;
private LinearLayout contentsLayout;
private View myView;
public FragTopics() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
session = new ManSession(getActivity().getApplicationContext());
courseId = getArguments().getString("courseId");
topicId = Long.parseLong(getArguments().getString("topicId"));
courseName = getArguments().getString("courseName");
new HeavyWork().execute();
// Create empty view because i need to return something
myView = new View(getActivity());
return myView;
}
private class HeavyWork extends AsyncTask<Void, Void, Void> {
private ProgressDialog dialog;
final FragmentUpdater activity = (FragmentUpdater) getActivity();
// Do the long-running work in here
#Override
protected Void doInBackground(Void... params) {
MoodleCourseContent[] courseTopics = new ManContents(getActivity()
.getApplicationContext()).getContent(courseId);
MoodleCourseContent singleTopic = new ManContents(getActivity()
.getApplicationContext()).getTopic(topicId, courseTopics);
// This createTopics call another methods from the fragment class to
// get the data and create views
myView = createTopics(singleTopic, courseName, courseId, topicId);
return null;
}
protected void onPreExecute() {
dialog = new ProgressDialog(getActivity());
dialog.show();
}
// This is called when doInBackground() is finished
#Override
protected void onPostExecute(Void ignore) {
activity.updater(myView);
dialog.dismiss();
}
}
My interface:
public interface FragmentUpdater {
public void updater(View param);
}
My main activity where the interface is implemented:
#Override
public void updater(View param) {
ViewGroup vg = (ViewGroup) findViewById (R.id.main_content);
vg.removeAllViews();
vg.addView(param);
}
My layouts are created programmatic and this is structure:
->LinearLayout
->TextView
->ScrollView
->LinearLayout
My main question is how to display a progress bar while fragment 2 is initialized without freezing the UI and if my above approach is correct, how to update the view in the fragment after the heavyWork is done in other words after asyncTask is done doing the background work
After remove all view and add the new one onBackpressed will overlap my view with the previous fragments views

Your fragment is headless. So don't implement the onCreateView method. Move that code to onCreate. Then in the async task, use onPostExecute to signal the parent activity with updated information for it to display. The parent activity of this fragment can then decide how the UI should react to this information. Make sure you avoid communicating with the parent activity anywhere other than the onPostExecute (or onPublishProgress, if you're updating) method, since a) the activity can be null for a number of reasons, including a device rotation and b) these methods are gauranteed to run on the UI thread

Related

Reusing layout created on Fragment.onCreateView() to avoid inflation when showing multiple times

I am using a DialogFragment to display a 'modal' bottom sheet menu (more info here: https://material.io/develop/android/components/bottom-sheet-dialog-fragment/). Since it contains a kind of context menu for the items contained in a RecyclerView, it may be shown multiple times during runtime.
However, always DialogFragment.show() is called, Fragment.onCreateView() is also called, which leads to layout inflation, which can(?) be considered as a 'heavy' task to be computed in the UI thread, which I want to avoid for performance reasons. So to avoid layout inflation every time the DialogFragment is shown, I created a ViewGroup member object pointing to the View being returned Fragment.onCreateView() in order to be reused, like this:
public class BottomMenu extends BottomSheetDialogFragment {
private ViewGroup mLayout;
private TextView mLabel;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
if (mLayout == null) {
mLayout = (ViewGroup) LayoutInflater.from(getContext()).inflate(R.layout.bottom_sheet, container, false);
mLabel = mLayout.findViewById(R.id.bottom_sheet_label);
}
return mLayout;
}
#Override
public void onDismiss(#NonNull DialogInterface dialog) {
super.onDismiss(dialog);
// The view cannot be reused if it's already attached to the previous parent view
((ViewGroup) mLayout.getParent()).removeView(mLayout);
}
public void setLabel(String label) {
mLabel.setText(label)
}
}
But once used for the first time, such view must be detached from the Fragment container view to be reused (see onDismissed() overriden method on posted snippet), which seems like a nasty workaround
So I post this question to check if anyone knows a better approach to reuse the layout for the same Fragment
More details here:
public class ActivityMain extends AppCompatActivity {
private BottomMenu mBottomMenu;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
[...]
mBottomMenu = new BottomMenu();
}
#Override
public boolean onLongClick(View v) {
mBottomSheet.setLabel(label);
// The following calls onCreateView() in Fragment, so try to return
// there the previously inflated layout, if any
mBottomSheet.show(getSupportFragmentManager(), "TAG?");
return true;
}
}
It is already a nice practice as long as you don't surrender to any possible bugs.. However there are one or two things I want to let you know about resuing dialogFragment.
public class BottomMenu extends BottomSheetDialogFragment {
private ViewGroup mLayout;
private TextView mLabel;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
if (mLayout == null) {
mLayout = (ViewGroup) LayoutInflater.from(getContext()).inflate(R.layout.bottom_sheet, container, false);
mLabel = mLayout.findViewById(R.id.bottom_sheet_label);
} else if(mLayout.getParent()!=null) { // it's not a lot of code. just a few lines……
((ViewGroup)mLayout.getParent()).removeView(mLayout);
}
return mLayout;
}
}
One thing is about nested fragments. When the dialogFragment hold a viewpager and the viewpager have serveral sub-fragments, you must reset the viewpager's adapter on the reusing-call of onCreateView. The reason is that after closing the dialogFragment, the old fragmentManager returned by getChildFragmentManager() is no longer valid, and it should be updated.
... onCreateView(...)
if (mLayout == null) {
...
} else {
...
viewpager.setAdapter(new MyFragmentAdapter(getChildFragmentManager(), fragments));
}
If this step is omitted, you may observe strange behaviours when reusing the dialogFragment, such as recyclerviews in the sub-fragments stop updating in response to NotifyDatasetChanged, but if you scroll it, it will update.
Another thing is that I tend to use WeakRefernce to hold the dialogFragment to be reused. I even have an array of them.
In java applications, if you don't use similar mechanism, you can see rapid surge in memory usage when the user open and close the same dialog again and again. So at least it's not a bad practice to reuse dialogs when it's necessary.

Android Listener example to hold current TextView elements on Fragment

I have a main Activity and several Fragments. On each fragment I have several TextView elements. I want to change font size of TextViews on the current displayed Fragment from Main Activity. Therefore I want to hold list of TextView elements on the currentFragment using a Listener.
But I don't know how to implement such Listener?
Is the listener right way to do that?
If there is a another way to achieve this, I wanted to know. Any answers welcome. Thanks.
If I got your question right, you could just access the current fragment's TextView object from the Main Activity and use the setTextSize() method.
You declare a list of TextViews and provide a method to add the textviews to the list.
And you also provide a method to signal the activation state. This method will go through the list of textviews calling the changes you need one by one.
From your main activity you will call fragment.changeListeningTextViews();
This goes in the Fragment:
private ArrayList<TextView> listeningTextViews;
public void addListeningTextview(TextVew tv){
//Here check if the text view is already added not to add it twice.
listeningTextViews.add(tv);
}
public void changeListeningTextViews(){
for(TextView tv : listeningTextViews){
tv.setFont(...);
//What you want called on each TextVeiw
}
}
In onCreateView you add the text views to the list:
TextView textView1 = (TextView) rootView.findViewById(R.id.aview);
addListeningTestview(textView1);
TextView textView2 = (TextView) rootView.findViewById(R.id.anotherview);
addListeningTestview(textView2);
...
Note: If it were the other way round when you need the Main Activity to listen on events coming from the fragment the solution would be different and the it would involve a custom Listener interface to be implemented by the Main Activity.
I had implemented as below. It works well fine but I don't know how clear solution it is.
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
//
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action buttons
switch(item.getItemId()) {
case R.id.smallFontSize:
FontSizeHelper.updateFontSize(Constants.fontSize1);
return true;
case R.id.mediumFontSize:
FontSizeHelper.updateFontSize(Constants.fontSize2);
return true;
case R.id.largeFontSize:
FontSizeHelper.updateFontSize(Constants.fontSize3);
return true;
case R.id.extraLargeFontSize:
FontSizeHelper.updateFontSize(Constants.fontSize4);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
public class FontSizeHelper {
public static List<View> viewElements = new ArrayList<>();
public static void initFontElements(){
viewElements = new ArrayList<>();
}
public static void addFontEelements(View view){
viewElements.add(view);
}
public static void updateFontSize(int fontSize){
for(View v : viewElements){
if(v instanceof TextView){
((TextView) v).setTextSize(fontSize);
}
}
}
}
public class FragmentA extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.a_fragment, container, false);
//initialize empty list for View objects.
FontSizeHelper.initFontElements();
//add View object/TextViews into list
FontSizeHelper.addViewEelements(textView1);
FontSizeHelper.addViewEelements(textView2);
FontSizeHelper.addViewEelements(textView3);
//..etc
}}

Indeterminate ProgressBar while loading content for PreferenceFragment's ListPreference

In my PreferenceFragment there is a ListPreference which is programmatically populated at onCreate(). Thus, there is a little lag at the fragment start. In order to avoid it I would like to populate the ListPreference only when the preference has been pressed, and put a indeterminate ProgressBar while the content is loading.
How could I implement this? Do I really need to rewrite the adapter? How can I get the view for the ListPreference to pass to View.OnClickListener?
I hope this is not a trivial question, I have googled for a while but I didn't understand how should I actually implement the whole thing.
Thank you in advance.
I've created a custom PreferenceFragment for this.
You can extend this one instead of a PreferenceFragment and delay your call to addPreferencesFromResource till after all loading is done, which will then hide the ProgressBar and display your content.
public class ProgressBarPreferenceFragment extends PreferenceFragment {
private FrameLayout progress;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
showLoading((ViewGroup) view);
return view;
}
#Override
public void addPreferencesFromIntent(Intent intent) {
super.addPreferencesFromIntent(intent);
hideLoading();
}
#Override
public void addPreferencesFromResource(int preferencesResId) {
super.addPreferencesFromResource(preferencesResId);
hideLoading();
}
private void hideLoading() {
ViewGroup root = (ViewGroup) getView();
if ((root != null) && (progress != null)) {
root.removeView(progress);
}
}
private void showLoading(ViewGroup root) {
progress = new FrameLayout(root.getContext());
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER;
progress.addView(new ProgressBar(root.getContext()), lp);
LayoutParams lp2 = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
root.addView(progress, lp2);
}
}
You could use an AsyncTask to populate your ListPreference asynchronously and then remove the indeterminate progress bar upon completion.
First, extend AsyncTask like so:
private class PopulateListPreferenceTask extends AsyncTask<CharSequence, Void, Void> {
protected Long doInBackground(CharSequence... entries) {
// Code to populate ListPreference in here
}
protected void onPostExecute() {
// Add code here if necessary to retrieve the progress bar view, i.e. findViewById
myIntederminateProgressBar.setVisibility(View.GONE);
}
}
Have your indeterminate progress bar shown initially. Then, in your fragment's onActivityCreated method, call:
new PopulateListPreferenceTask().execute(/*CharSequence entries go here*/);
A couple notes:
I am assuming that you would want to provide Charsequences as
input, but you can use any other type.
If you need the AsyncTask to receive the Activity context you can
overload the constructor to receive it as a parameter and then
store it in a field.

Control a View in a Fragment after Asynctask

I have a Fragment that loads data from web
So I'd like to make a RelativeLayout to show to user that data is still loading
When finished I want to make my RelativeLayout disappear
But here's the problem..
On my Xml of Fragment I put my RelativeLayout with my loading bar visible..
So my fragments goes throught this step:
1)onCreate() {inside this I have Asynctask.execute()
2)onCreateView() {And here I can manage my RelativeLayout with loadingbar through Inflater and View}
3)Asyntask.onPostExecute() {And here I want to make disappear my relativelayout..}
BUT in Asynctask there's no way to access to my relativelayout, and of course if I try app crashes because of NullPointerException [Obvious]
How can I manage this problem?
It seems he did the following:
private RelativeLayout mRelativeLayout;
mRelativeLayout = findViewById(R.Layout.relativeLayoutId);
And use it later on in the class like
mRelativeLayout.doTheMagic();
Modify the constructor of your AsyncTask to accept a reference to the View, then you can modify it during the onPreExecute() and onPostExecute(Result) methods. Why aren't you just using a ProgressDialog instead? Much easier.
That's how I solved:
public class HomeFragment extends Fragment {
RelativeLayout mLoading_Bar;
public void onCreate(Bundle savedInstanceState) {
Asynctask.execute();
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View cView = inflater.inflate(R.layout.fragment_home, container, false);
mLoading_Bar = (RelativeLayout) cView.findViewById(R.id.fragment_home_loadingdata_layout);
return cView;
}
class ASynctask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... args) {
//do something
}
protected void onPostExecute(Void string) {
mLoading_Bar.setVisibility(View.GONE);
}
}
}
And that's all folks

Which lifecycle method will be invoked when Activity is showing?

Sometimes I need to do some operations(e.g. changing layout) when the activity is just showing. What I do now is using post():
public class MyActivity extends Activity {
#Override
public void onCreate() {
...
container.post(new Runnable(){
resize(container);
});
}
}
Is there any lifecycle method like onCreate can be used to simplify the code, that I don't need to call post?
#Override
public void onX() {
resize(container);
}
I think you mean do something after the UI is displayed.
Using a global layout listener has always worked well for me. It has the advantage of being able to remeasure things if the layout is changed, e.g. if something is set to View.GONE or child views are added/removed.
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// inflate your main layout here (use RelativeLayout or whatever your root ViewGroup type is
LinearLayout mainLayout = (LinearLayout ) this.getLayoutInflater().inflate(R.layout.main, null);
// set a global layout listener which will be called when the layout pass is completed and the view is drawn
mainLayout.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
// at this point, the UI is fully displayed
}
}
);
setContentView(mainLayout);
http://developer.android.com/reference/android/view/ViewTreeObserver.OnGlobalLayoutListener.html

Categories

Resources