Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OnKeyDown inside WebView fragment

I made a Fragment, containing a WebView and I wanted to define an onKeyDown() to get back from one web page to the previous one. I did it, but the weirdest part for me was to share a WebView variable from my Fragment class to my Activity class because I can't define onKeyDown() in the Fragment. So I just defined a get method and made it static. But I wonder if it was a real mistake and my app can seriously crash in some circumstances.

My Fragment code:

public class BrowserFragment extends Fragment {
    private static WebView webView;

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

    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState){
        View v = inflater.inflate(R.layout.fragment_activity, parent, false);

        getActivity().setTitle(R.string.title_rus);

        webView = (WebView) v.findViewById(R.id.webView);
        webView.setWebViewClient(new SwingBrowserClient());
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        Uri data = Uri.parse("http://www.swinginmoscow.ru/m/");
        webView.loadUrl(data.toString());

        return v;
    }

    public static WebView getWebView() {
        return webView;
    }
}

And my Activity code:

public class MainBrowserActivity extends SingleFragmentActivity {

    @Override
    protected Fragment createFragment() {
        return new BrowserFragment();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK) && BrowserFragment.getWebView().canGoBack()) {
            BrowserFragment.getWebView().goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
}
like image 407
Mabjik Avatar asked Apr 22 '14 22:04

Mabjik


1 Answers

It might work, but it is not a good idea. It could very well cause a crash if you don't handle the Fragment correctly or are somewhere in your code a little careless regarding its lifecycle. But there is an easy way around this. Instead of using a static method, save the instance and call methods on the instance itself. This way you can check if the instance is null and if not the Fragment can handle calls to goBack() or canGoBack() itself:

public class MainBrowserActivity extends SingleFragmentActivity {

    BrowserFragment browserFragment = null;

    @Override
    protected Fragment createFragment() {
        this.browserFragment = BrowserFragment.newInstance();
        return this.browserFragment;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && this.browserFragment != null && this.browserFragment.canGoBack()) {
            this.browserFragment.goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }
}

As you can see the BrowserFragment instance is saved and then methods like goBack() or canGoBack() are called on the BrowserFragment itself. Of course you have to implement these methods in the BrowserFragment but that should not be a problem:

public class BrowserFragment extends Fragment {

    public static BrowserFragment newInstance() {
        BrowserFragment fragment = new BrowserFragment();
        return fragment;
    }

    private WebView webView;

    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState){
        View v = inflater.inflate(R.layout.fragment_activity, parent, false);

        getActivity().setTitle(R.string.title_rus);

        webView = (WebView) v.findViewById(R.id.webView);
        webView.setWebViewClient(new SwingBrowserClient());
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        Uri data = Uri.parse("http://www.swinginmoscow.ru/m/");
        webView.loadUrl(data.toString());
        return v;
    }

    public boolean canGoBack() {
        return this.webView != null && this.webView.canGoBack();
    }

    public void goBack() {
        if(this.webView != null) {
            this.webView.goBack();  
        }  
    }
}

I made a few extra improvements to your code. First of all I added null checks to prevent any possible NullPointerExceptions and secondly it is recommend to always use a static factory method to create new instances of Fragments. That is what the static newInstance() method is that I added to the BrowserFragment. The advantage of that is that you can implement a method which takes care of setting up the BrowserFragment for you regardless of where you use it. You can add parameters to newInstance() method to pass some value to the BrowserFragment or to add some required listener etc but since you don't pass any values to the BrowserFragment the newInstance() method remains pretty empty. Nevertheless it is best practice to always use such factory methods even if they only call new BrowserFragment().

Generally this approach is much better. Especially from a architecture perspective because you don't directly interact with the WebView in the Activity. The WebView doesn't have anything to do with the Activity, it is part of the implementation of the BrowserFragment and as such the Activity should not know that there even is a WebView. How calls to goBack() or canGoBack() are implemented or what they exactly do is of no interest to the Activity. The Activity just tells the BrowserFragment "I want to go back" and the BrowserFragment does the work. This separates the responsibilities better and makes the code more readable, more clear and more maintainable.

EDIT:

Also I don't know of a SingleFragmentActivity but generally any Activity implements onBackPressed() method. You don't have to override onKeyDown() to catch a back key event. You can just do something like this:

@Override
public void onBackPressed() {
    if (this.browserFragment != null && this.browserFragment.canGoBack()) {
        this.browserFragment.goBack();
    } else {  
        // The back key event only counts if we execute super.onBackPressed();
        super.onBackPressed();
    }
}

If you have any other questions please feel free to ask!

like image 142
Xaver Kapeller Avatar answered Nov 01 '22 23:11

Xaver Kapeller