以前、自作Viewを作っている時にコンパイル時や実機などでの確認時は特に問題なかったのに、Layoutファイルに自作Viewを乗っけてBuildするとExceptionがはかれて描画されない(Previewに表示されない)なんてことがありました。

これはちゃんと回避方法があるので、その方法のご紹介です。

例としてWebViewを継承してLayoutファイルだけで設定できるようにした自作クラスを紹介します。

ソースコード

今回のサンプルはこんなクラスです。

CustomWebView
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class CustomWebView extends WebView {
private static final String TAG = CustomWebView.class.getSimpleName();
private final CustomWebView self = this;
public CustomWebView(Context context, AttributeSet attrs) {
super(context, attrs);
initializeView(context, attrs);
}
public CustomWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initializeView(context, attrs);
}
private void initializeView(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomWebView);
boolean useJavascript = typedArray.getBoolean(R.styleable.CustomWebView_use_javascript, true);
String urlStr = typedArray.getString(R.styleable.CustomWebView_url_string);
WebSettings webSettings = getSettings();
webSettings.setJavaScriptEnabled(useJavascript);
webSettings.setJavaScriptCanOpenWindowsAutomatically(useJavascript);
setVerticalScrollbarOverlay(true);
setWebViewClient(new CustomWebViewClient());
loadUrl(urlStr);
}
}
res/values/attr.xml
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomWebView">
<attr name="url_string" format="string"/>
<attr name="use_javascript" format="boolean"/>
</declare-styleable>
</resources>
res/layout/activity_main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<jp.shinobi.admax.android.sample.CustomWebView
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:url_string="http://ninja.co.jp"
custom:use_javascript="true"
/>
</RelativeLayout>

この状態でLayoutファイルを開いて、Previewを見てみましょう

エラーが発生する

こんなエラーが出力されると思います。

ScreenShot53

ちなみにErrorのログは以下のように書かれています。

1
2
3
4
5
6
java.lang.NoSuchMethodError: jp.shinobi.admax.android.sample.CustomWebView.getSettings()Landroid/webkit/WebSettings;
at jp.shinobi.admax.android.sample.CustomWebView.initializeView(CustomWebView.java:41)
at jp.shinobi.admax.android.sample.CustomWebView.<init>(CustomWebView.java:27)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
~ 省略 ~

とっても長いので省略しました。

大事なのは頭3行。

1
2
3
java.lang.NoSuchMethodError: jp.shinobi.admax.android.sample.CustomWebView.getSettings()Landroid/webkit/WebSettings;
at jp.shinobi.admax.android.sample.CustomWebView.initializeView(CustomWebView.java:41)
at jp.shinobi.admax.android.sample.CustomWebView.<init>(CustomWebView.java:27)

今回僕が自作したクラスでエラーが吐かれています。

しかもgetSettings()のメソッドがないってさ。

ということでまずはエラーの対処をしましょう。

View.isInEditMode()を使ってエラー回避

「Tip: Use View.isInEditMode() in your custom views to skip code or show sample data when shown in the IDE」とPreviewの画面にも出力されている通り、View.isInEditMode()を使います。

View.isInEditMode()を使ってコードをスキップするか、IDEにサンプルデータを表示してみてくださいっていう意味でしょうか。

ではjava.lang.NoSuchMethodErrorということなので、このメソッドを呼ぶ前にView.isInEditMode()を使用します。

以下のように使用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void initializeView(Context context, AttributeSet attrs) throws Exception {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomWebView);
boolean useJavascript = typedArray.getBoolean(R.styleable.CustomWebView_use_javascript, true);
String urlStr = typedArray.getString(R.styleable.CustomWebView_url_string);
// getSettings()を呼ぶ前に確認
if (isInEditMode()) {
// 編集モードだったら処理終了
return ;
}
WebSettings webSettings = getSettings();
webSettings.setJavaScriptEnabled(useJavascript);
webSettings.setJavaScriptCanOpenWindowsAutomatically(useJavascript);
setVerticalScrollbarOverlay(true);
setWebViewClient(new CustomWebViewClient());
loadUrl(urlStr);
}

WebView.getSettings()を呼ぶ前にisInEditMode()trueの時は処理を終わらせるようにしました。

一度buildしてリフレッシュするとエラーが消えると思います。

ScreenShot54

これで問題は解決しました。

View.isInEditModeとは

View.isInEditMode()とはどんなメソッドか紹介します。

Indicates whether this View is currently in edit mode. A View is usually in edit mode when displayed within a developer tool. For instance, if this View is being drawn by a visual user interface builder, this method should return true. Subclasses should check the return value of this method to provide different behaviors if their normal behavior might interfere with the host environment.

「このViewが現在編集モードであるかどうかを返します。開発者用ツール内に表示される際の表示が編集モードです。例えば、ビジュアルユーザーインターフェースビルダーによって描画されている場合はこのメソッドはtrueを返す必要があります。サブクラスは、通常の動作がホスト環境に干渉する可能性がある場合は別の動作を提供をするために、このメソッドの戻り値をチェックする必要があります。」

という意訳で大丈夫でしょうか。

今回はWebView#getSettings()が「通常の動作がホスト環境に干渉する」という点に引っかかってしまったのでしょう。

今回みたいなエラーは珍しくありません。

なのでView.isInEditMode()を使って潔くエラーを回避して、どうしても確認したいときはエミュレーターなり、実機で確認するべきだと思います。

終わりに

このエラーとずっと戦っていましたが、完成間近になってView.isInEditMode()をしっかり使えるようになりました。

Previewがしっかりエラーを吐き出してくれるので、そのエラーをお粗末にしないでおけばよかったなと今反省しています。

以上になります。