Preloading of an Android WebView

Coding for Android can be tough at times. Especially when things one would take for granted aren't supported at all. Such as an option for a fully justified text in a TextView. After having tried a number of different solutions I eventually gave up on using a TextView and resorted to a WebView instead. Of course, the first problem I hit was bad performance. While the text started to show in a way it was meant to, it took about a second to load despite the fact there was no network traffic involved at all. Having to show a spinner for a tiny piece of text loaded from local resources felt like a snap in the face. I started to dig into the issue with the TextView once again. I even ended up implementing full justification on my own, but when trying to apply it to Android I quickly discovered there was much more to worry about. Scrap that, back to the sluggish WebView, trying to preload it this time. That finally worked. So here is my take on how to preload a simple dialog containing a WebView.

First of all, I am not sure if what I've done is totally correct, but it worked for me at least. Now, the app is a simple quiz game. Once the right choice is made, the app displays an explanatory popup.



I found it reasonable to expect that once the quiz is presented, there is a good chance the popup will show as a result of the user having made the correct guess. Without further ado, here is how I went about the preloading of the popup.

The popup QuizSolutionDialog inherits from a DialogFragment, has the web view as a private member and exposes a custom preLoad callback.
 public class QuizSolutionDialog extends DialogFragment {  
    public static final String JUSTIFIED_TEXT =  
       "<html><body><p align=\"justify\"><font color=\"%s\">%s" +
       "</p></body></html>";  
    
    private View dialogView;

    private WebView explanationView;  
    ..  
    // This is called from the Game Fragment 
    // whenever quiz data become available  
    public void preLoad(String explanation, 
      LayoutInflater inflater, Resources resources) {  
       
       // Inflating of the respective layout 
       //provides access to the relevant UI components  
       dialogView = inflater
          .inflate(R.layout.fragment_dialog_quiz_solution, null);  

       explanationView = (WebView) dialogView
          .findViewById(R.id.explanation);  

       // Loading of data usually takes time  
       explanationView.loadData(
          String.format(JUSTIFIED_TEXT, color, explanation),   
          "text/html", "utf-8");  
   }  
 }  

The game itself is captured in another fragment and maintains a reference to the solution dialog. When a new quiz becomes available it instantiates the popup (line 9) and loads its web view with an explanatory text using the preLoad callback (line 15). Also, notice that the explanation text is shared between the two fragments (lines 12 - 14). That's part of a fallback solution explained below.
1:  public class GameFragment extends Fragment {  
2:    
3:    private QuizSolutionDialog quizSolutionDialog;  
4:    ..  
5:    
6:    // When a new quiz becomes available the popup  
7:    // is preloaded using the text explaining quiz's solution  
8:    private void loadQuizDialog(String explanation) {  
9:      quizSolutionDialog = new QuizSolutionDialog();  
10:      quizSolutionDialog.setOnClickListener(this);  
11:    
12:      Bundle args = new Bundle();  
13:      args.putString("explanation", currentQuiz.getExplanation());  
14:      quizSolutionDialog.setArguments(args);  
15:      quizSolutionDialog.preLoad(explanation,   
16:        getActivity().getLayoutInflater(), getResources());  
17:    }  
18:  }  
As a fallback, i.e. when the preLoad method isn't called in time, the dialog will be instantiated the standard way, as part of the fragment's lifecycle. The code excerpt below shows a call to preLoad from the onCreateView lifecycle callback. As you can see the explanation is pulled from the shared bundle and the other parameters are provided by the callback itself.
 public class GameFragment extends Fragment {  
   
   private View dialogView;  
   ..  
   @Override  
   public View onCreateView(LayoutInflater inflater,   
     ViewGroup container, Bundle savedInstanceState) {  
   
     if (dialogView == null) {  
       String explanation = getArguments().getString("explanation");  
       preLoad(explanation, inflater, getResources());  
     }  
     ..  
     return dialogView;  
   }  
 }  

When it's on time to show the dialog, the only thing left to do is to call its show method and the popup displays instantly, which adds to a smooth user experience.

In summary, the solution works, but there are drawbacks I am not too happy about. 

First of all, the preload itself is nothing but a last resort. Ideally, I come up with a specialised TextView providing functionality I need (fully justified text in my case) so that I don't have to deal with a WebView in the first place. Secondly, the custom callback preLoad doesn't match fragment's lifecycle, yet it relies on the same dependencies (inflater) which need to be explicitly provided to it.

This post is part of Google App Engine and Android - Pros and Cons, Challenges