7

Today I faced a problem with WebView inside a Fragment using Android Navigation Component. In my case, in Fragment1 there is button under WebView. User needs to scroll down to click it. After clicking it, a new destination is opened (Fragment2). Then, when user goes back to Fragment1, WebView is reloaded, and viewport is scrolled to the top again.

It's extremely annoying. User might been scrolling down for minutes, and it always throws him to the very top after coming back. Can I somehow prevent it from reloading / scrolling back to top?

Example code could be as simple as that:

class Fragment1 : Fragment() {

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    webView.loadData(getString(R.string.some_very_long_html), "text/html", "UTF-8")
    buttonBelowWebView.setOnClickListener {
      navigateToFragment2()
    }
  }

}

If WebView contains videos, loading might take even up to 5 seconds. It's terrible experience.

I tried adding flag to load only one time, but then WebView is empty when navigating back from Fragment2:

class Fragment1 : Fragment() {

  var loaded = false
  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    if(!loaded) {
      webView.loadData(getString(R.string.some_very_long_html), "text/html", "UTF-8")
      loaded = true
    }
  }

}

Note that in real life I use ViewModels and observe data from rest api, but the problem stays the same.

Also, example code I provided is written in Kotlin, but if there is solution in Java, then it's appreciated too.

Is it possible to not reload WebView and reset scroll position when navigating back from nested destination?

xinaiz
  • 7,744
  • 6
  • 34
  • 78

5 Answers5

11

This is how fragments work in their basic form (which is what the Navigation Components use)

The fragments themselves are saved, but their views are destroyed.

This is what happens in your scenario:

  • Fragment 1 created
  • Fragment 1 view created (This contains the webview)
  • Button clicked
  • Fragment 1 view destroyed
  • Fragment 2 created
  • Fragment 2 view created
  • Back clicked
  • Fragment 1 view re-created
  • Fragment 2 view destroyed
  • Fragment 2 destroyed

If you weren't using Navigation Components, this would be very easy to solve using non-hacky ways.
You could simply use .hide() combined with .add() on a FragmentTransaction instead of .replace(), and the fragments views would not be destroyed.

But seeing as you're using Navigation Components, you aren't handling the fragment transactions yourself.

A workaround would be the one posted here:
https://stackoverflow.com/a/55039009/2877453

As the fragment and their variables are saved, you could save your view as a variable in the fragment.
Then check in onCreateView, if the variable is null, inflate it like normally, if not null, then return your saved view.

Moonbloom
  • 7,738
  • 3
  • 26
  • 38
  • Thanks! That's exactly what I did in the end! I was not sure if it's good solution though. I will post it as answer when I have some free time. – xinaiz May 18 '19 at 14:10
2

Use this code before change fragment:-

if (getVisibleFragment() instanceof Fragment1){
      // do nothing
}else{
      // replace fragment
}


    public Fragment getVisibleFragment(){
                FragmentManager fragmentManager = getSupportFragmentManager();
                List<Fragment> fragments = fragmentManager.getFragments();
                if(fragments != null){
                    for(Fragment fragment : fragments){
                        if(fragment != null && fragment.isVisible())
                            return fragment;
                    }
                }
                return null;
    }
2

Please changes to this

class Fragment1 : Fragment() {

 @Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    webView?.loadData(getString(R.string.some_very_long_html), "text/html", "UTF8")
   }

}

give a try

patel dhaval r
  • 1,237
  • 1
  • 8
  • 17
2

Try this -

this will be implemented in the fragment that has webview

 @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    if (view == null) {
        // inflate your layout here
        return view = inflater.inflate(R.layout.community_home_layout, container, false);
     }else {
      return view;
  }
Sujeet Kumar
  • 256
  • 1
  • 11
1

Hey there when ever you come in fragment first time or coming back its method onViewCreated always call again and again, if you want something like you don't want to reset you can do it by one global boolean check because that global variable will not be change after fragment resumes

Like this

var shouldLoad = true

And then what ever you want to do

if(shouldLoad){
  //do your work

  //setting false to your boolean
  shouldLoad = false
}else{
  //if you need something else
}
Hello world
  • 80
  • 1
  • 10
  • Unfortunately, if I do this webView is empty when I go back. `onCreateView` is called when I come back, so new layout is inflated, thus "new" webView is empty. – xinaiz May 16 '19 at 07:54
  • This does not work as onCreateView is called and view inflated, if more details is required please add. – Chris Herbst Nov 19 '19 at 13:07