1. ホーム
  2. アンドロイド

Android 開発において、null オブジェクトの参照で仮想メソッドを呼び出そうとする。

2022-01-24 16:06:40

概要を説明します。

Androidレイアウトファイルの読み込み中にエラーが発生しました。
java.lang.NullPointerException: Null オブジェクト参照で仮想メソッド '.........' を呼び出そうとしました。
このエラーは、正しい View 環境で findViewById() メソッドを使用しなかったことが原因である可能性があります。


Androidは初めてで、何もわからない。

ListViewを作成し、ItemをクリックするとImageViewを含むダイアログボックスが表示されるという機能を、以下のコードで実装したいと思います。


ImageView photo;
Bitmap bitmap = BitmapFactory.decodeStream(
        getContext().getContentResolver().openInputStream(uri));
ImageView photo = getView().findViewById(R.id.photo);
photo.setImageBitmap(bitmap);
Builder builder = new AlertDialog.Builder(getContext());
builder.setView(R.id.dialog);
/* Define other buttons */
AlertDialog dialog = builder.create();
dialog.show();





ダイアログ用に新しいレイアウトファイルが作成され、このR.id.photoはそのファイルに配置されます。

しかし、ランタイムはエラーを報告し続けます。

java.lang.NullPointerException: NULLオブジェクトの参照に対して仮想メソッド「............」を呼び出そうとしています。

原因は、写真が正しく初期化されていないためです。

しばらく考えてから、明らかに関連付けしているし、コンパイルも問題ないので、正しく初期化されているはずです。

そこで、今まで無差別に使っていたfindViewById()の原理を考えてみた。このエラーで、もしかしたらfindViewById()が正しく設定されていないのかもしれないと気づきました。

通常、findViewById()の使用はActivityのOnCreate()メソッド内などです。


button = (Button)findViewById(R.id.button);





ここで、(Button)は省略可能です。

ここでのfindViewById()関数は、Activityクラスのものです。

public <T extends View> T findViewById(@IdRes int id) {
        return getWindow().findViewById(id);
    }

Fragment の中で findViewById() を使う場合、Fragment の View オブジェクトを作成し、View オブジェクトの FindViewById() メソッドを呼び出す必要があります。

View v = inflater.inflate(R.layout.fragment, container, false);
listView = v.findViewById(R.id.list_view);

ViewクラスのfindViewById()メソッドの場所は以下の通りです。

@Nullable
    public final <T extends View> T findViewById(@IdRes int id) {
        if (id == NO_ID) {
            return null;
        }
        return findViewTraversal(id);
    }

細かいことは調べない、(どうせ私のレベルではわからないから)

上のエラーが出たのは、どのfindViewById()を使えばいいのかが分からなかったからです。なぜなら、getView()の呼び出しは現在表示されているViewを取得することになっており、ここではDialogダイアログに固有の新しいレイアウトファイルを表示しようとしているので、ImageViewを初期化するために現在のViewオブジェクトを使用することができないからです。

解決策としては、Dialog クラスを継承した新しいクラスを作成し、そのクラスで表示する要素を定義し、OnCreate() メソッドを以下のように再初期化することです。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LayoutInflater inflater = (LayoutInflater) mContext
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View layout = inflater.inflate(R.layout.photo_dialog, null);
        photo = layout.findViewById(R.id.photo);
        try{
            Bitmap bitmap = BitmapFactory.decodeStream(
                    getContext().getContentResolver().openInputStream(uri));
            photo.setImageBitmap(bitmap);
        }catch (Exception e){
            e.printStackTrace();
        }
        this.setContentView(layout);
    }
}

オリジナルのFragmentは、2つのシンプルなセンテンスで構成されていました。

MyDialog myDialog = new MyDialog(getContext());
myDialog.show();





要するに、複雑なDialogを作るには、Dialogのメソッドをオーバーライドして、元のクラスにゴチャゴチャ詰め込まない方が良いということです。

初心者の私は、自分のコードをOOPにしたいと思うこともありますが、他の方の素晴らしいコードを見ると、デカップリングはコードの長さではなく、特定の機能が必要かどうかで決まるので、経験が必要だと感じています。