The Harder Way: WebView

Using an implicit intent to display the photo page is easy and effective. But what if you do not want your app to open the browser?

Often, you want to display web content within your own activities instead of heading off to the browser. You may want to display HTML that you generate yourself, or you may want to lock down the browser somehow. For apps that include help documentation, it is common to implement it as a web page so that it is easy to update. Opening a web browser to a help web page does not look professional, and it prevents you from customizing behavior or integrating that web page into your own UI.

When you want to present web content within your own UI, you use the WebView class. We are calling this the harder way here, but it is pretty darned easy. (Anything is hard compared to using implicit intents.)

The first step is to create a new activity and fragment to display the WebView. Start, as usual, by defining a layout file and naming it fragment_photo_page.xml. Make ConstraintLayout the top-level layout. In the visual editor, drag a WebView into the ConstraintLayout as a child. (You will find WebView under the Containers section.)

Once the WebView is added, add a constraint for every side to its parent. That gives you the following constraints:

  • from the top of WebView to the top of its parent

  • from the bottom of WebView to the bottom of its parent

  • from the left of WebView to the left of its parent

  • from the right of WebView to the right of its parent

Finally, change the height and width to Any Size and change all the margins to 0. Oh, and give your WebView an ID: web_view.

You may be thinking, That ConstraintLayout is not useful. True enough – for the moment. You will fill it out later in the chapter with additional chrome.

Next, get the rudiments of your fragment set up. Create PhotoPageFragment as a subclass of the VisibleFragment class you created in the last chapter. You will need to inflate your layout file, extract your WebView from it, and forward along the URL to display as a fragment argument.

Listing 30.5  Setting up your web browser fragment (PhotoPageFragment.java)

public class PhotoPageFragment extends VisibleFragment {
    private static final String ARG_URI = "photo_page_url";

    private Uri mUri;
    private WebView mWebView;

    public static PhotoPageFragment newInstance(Uri uri) {
        Bundle args = new Bundle();
        args.putParcelable(ARG_URI, uri);

        PhotoPageFragment fragment = new PhotoPageFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mUri = getArguments().getParcelable(ARG_URI);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_photo_page, container, false);

        mWebView = (WebView) v.findViewById(R.id.web_view);

        return v;
    }
}

For now, this is just a skeleton. You will fill it out a bit more in a moment. But first, create the containing PhotoPageActivity class using good old SingleFragmentActivity.

Listing 30.6  Creating web activity (PhotoPageActivity.java)

public class PhotoPageActivity extends SingleFragmentActivity {

    public static Intent newIntent(Context context, Uri photoPageUri) {
        Intent i = new Intent(context, PhotoPageActivity.class);
        i.setData(photoPageUri);
        return i;
    }

    @Override
    protected Fragment createFragment() {
        return PhotoPageFragment.newInstance(getIntent().getData());
    }
}

Switch up your code in PhotoGalleryFragment to launch your new activity instead of the implicit intent.

Listing 30.7  Switching to launch your activity (PhotoGalleryFragment.java)

public class PhotoGalleryFragment extends VisibleFragment {
    ...
    private class PhotoHolder extends RecyclerView.ViewHolder
            implements View.OnClickListener{
        ...
        @Override
        public void onClick(View v) {
            Intent i = new Intent(Intent.ACTION_VIEW, mGalleryItem.getPhotoPageUri());
            Intent i = PhotoPageActivity
                .newIntent(getActivity(), mGalleryItem.getPhotoPageUri());
            startActivity(i);
        }
    }
    ...
}

And, finally, add your new activity to the manifest.

Listing 30.8  Adding activity to manifest (AndroidManifest.xml)

<manifest ... >
    ...
    <application
        ...>
        <activity
            android:name=".PhotoGalleryActivity"
            android:label="@string/app_name" >
            ...
        </activity>

        <activity
            android:name=".PhotoPageActivity" />

        <service android:name=".PollService" />
        ...
    </application>

</manifest>

Run PhotoGallery and press on a picture. You should see a new empty activity pop up.

OK, now to get to the meat and actually make your fragment do something. You need to do three things to make your WebView successfully display a Flickr photo page. The first one is straightforward – you need to tell it what URL to load.

The second thing you need to do is enable JavaScript. By default, JavaScript is off. You do not always need to have it on, but for Flickr, you do. (If you run Android Lint, it gives you a warning for doing this. It is worried about cross-site scripting attacks. You can suppress this Lint warning by annotating onCreateView(…) with @SuppressLint("SetJavaScriptEnabled").)

Finally, you need to provide a default implementation of a class called WebViewClient. WebViewClient is used to respond to rendering events on a WebView. We will discuss this class a bit more after you enter the code.

Listing 30.9  Loading URL into WebView (PhotoPageFragment.java)

public class PhotoPageFragment extends VisibleFragment {
    ...
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_photo_page, container, false);

        mWebView = (WebView) v.findViewById(R.id.web_view);
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.setWebViewClient(new WebViewClient());
        mWebView.loadUrl(mUri.toString());

        return v;
    }
}

Loading the URL has to be done after configuring the WebView, so you do that last. Before that, you turn JavaScript on by calling getSettings() to get an instance of WebSettings and then calling WebSettings.setJavaScriptEnabled(true). WebSettings is the first of the three ways you can modify your WebView. It has various properties you can set, like the user agent string and text size.

After that, you add a WebViewClient to your WebView. To know why, let us first address what happens without a WebViewClient.

A new URL can be loaded in a couple of different ways: The page can tell you to go to another URL on its own (a redirect), or you can click on a link. Without a WebViewClient, WebView will ask the activity manager to find an appropriate activity to load the new URL.

This is not what you want to have happen. Many sites (including Flickr’s photo pages) immediately redirect to a mobile version of the same site when you load them from a phone browser. There is not much point to making your own view of the page if it is going to fire an implicit intent anyway when that happens.

If, on the other hand, you provide your own WebViewClient to your WebView, the process works differently. Instead of asking the activity manager what to do, it asks your WebViewClient. And in the default WebViewClient implementation, it says, Go load the URL yourself! And so the page will appear in your WebView.

Run PhotoGallery, press an item, and you should see the item’s photo page displayed in the WebView (just like the image on the right in Figure 30.1).

Using WebChromeClient to spruce things up

Since you are taking the time to create your own WebView, let’s spruce it up a bit by adding a progress bar and updating the toolbar’s subtitle with the title of the loaded page. Crack open fragment_photo_page.xml once again.

Drag in a ProgressBar as a second child for your ConstraintLayout. Use the ProgressBar (Horizontal) version of ProgressBar. Delete the WebView’s top constraint, and then set its height to Fixed so that you can easily work with its constraint handles.

With that done, create the following additional constraints:

  • from the ProgressBar to the top, right, and left of its parent

  • from the WebView’s top to the bottom of the ProgressBar

With that done, change the height of the WebView back to Any Size, change the ProgressBar’s height to wrap_content, and change the ProgressBar’s width to Any Size.

Finally, select the ProgressBar and move your attention to the properties window. Change the visibility to gone and change the tools visibility to visible. Rename its ID to progress_bar.

Your result will look like Figure 30.2.

Figure 30.2  Adding a progress bar

Screenshot shows adding a Progress bar in webview. The PhotoGallery app shows a screen with WebView at the center.

To hook up the ProgressBar, you will use the second callback on WebView: WebChromeClient. WebViewClient is an interface for responding to rendering events; WebChromeClient is an event interface for reacting to events that should change elements of chrome around the browser. This includes JavaScript alerts, favicons, and of course updates for loading progress and the title of the current page.

Hook it up in onCreateView(…).

Listing 30.10  Using WebChromeClient (PhotoPageFragment.java)

public class PhotoPageFragment extends VisibleFragment {
    ...
    private WebView mWebView;
    private ProgressBar mProgressBar;
    ...
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_photo_page, container, false);

        mProgressBar = (ProgressBar)v.findViewById(R.id.progress_bar);
        mProgressBar.setMax(100); // WebChromeClient reports in range 0-100


        mWebView = (WebView) v.findViewById(R.id.web_view);
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.setWebChromeClient(new WebChromeClient() {
            public void onProgressChanged(WebView webView, int newProgress) {
                if (newProgress == 100) {
                    mProgressBar.setVisibility(View.GONE);
                } else {
                    mProgressBar.setVisibility(View.VISIBLE);
                    mProgressBar.setProgress(newProgress);
                }
            }

            public void onReceivedTitle(WebView webView, String title) {
                AppCompatActivity activity = (AppCompatActivity) getActivity();
                activity.getSupportActionBar().setSubtitle(title);
            }
        });
        mWebView.setWebViewClient(new WebViewClient());
        mWebView.loadUrl(mUri.toString());

        return v;
    }
}

Progress updates and title updates each have their own callback method, onProgressChanged(WebView, int) and onReceivedTitle(WebView, String). The progress you receive from onProgressChanged(WebView, int) is an integer from 0 to 100. If it is 100, you know that the page is done loading, so you hide the ProgressBar by setting its visibility to View.GONE.

Run PhotoGallery to test your changes. It should look like Figure 30.3.

Figure 30.3  Fancy WebView

Screenshot shows Fancy WebView in Android.

When you press on a photo, PhotoPageActivity pops up. A progress bar displays as the page loads and a subtitle reflecting the subtitle received in onReceivedTitle(…) appears in the toolbar. Once the page is loaded, the progress bar disappears.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.7.194