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
Related
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.
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.
I got an custom class. Which works great.
public class FocusGameView extends SurfaceView implements Runnable
At the activity itself I want to put the 'FocusGameView' on a view that I already created on the xml file.
so I tried to use the 'inflate' like this:
public class FocusGame extends Activity {
FocusGameView fgv;
View v;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fgv= new FocusGameView(this);
v=(View) findViewById(R.id.frame_focus_game);
LayoutInflater mInflater;
mInflater = LayoutInflater.from(fgv.getContext());
v = mInflater.inflate(R.layout.activity_focus_game, null);
setContentView(R.layout.activity_focus_game);
}
The result of this code is opening the activity and set the layout. without put the custom view on the view itself.
I really hope you could help me with that.
Thanks in advance;
Yaniv.
You can only add a view to a view group, not a view, so lets image your View v is a RelativeLayout:
public class FocusGame extends Activity {
FocusGameView fgv;
RelativeLayout v;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_focus_game);
v=(View)findViewById(R.id.frame_focus_game);
fgv= new FocusGameView(this);
v.addView(fgv); //You only need to add the view to a parent to make it appear
}
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
MainActivity.java:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setTitle(R.string.app_name);
setContentView(new SampleView(this));
}
}
SampleView.java:
public class SampleView extends View {
#Override
protected void onDraw(Canvas canvas) {
if (certaincondition = true) {
//add elements to canvas etc
} else {
//How do I do the below? The layout is defined in xml.
//I do not want to use Intent. Please help me
//create a layout from resource R.layout.idAbout and transfer control.
}
}
}
Use a layout inflater:
View newRootViewElement;
LayoutInflater li = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
newRootViewElement= li.inflate(R.layout.idAbout, null);
You can inflate a layout using
View.inflate(getContext(), R.layout.idAbout, viewParent);
where viewParent is a ViewParent that will be the parent of the inflated view (and can be null).
But what are you trying to do? It's more than a little odd to start a new activity or to modify the view hierarchy from within onDraw(). You might want to post a runnable to a Handler that will do what you want on the next cycle of the event loop. To start a new activity (such as displaying “About” info for the app) you should take a look at the Intent class.