Search code examples
androidandroid-activityandroid-viewoncreatefindviewbyid

How do I use findViewById() in onCreate without it returning null?


I don't know how to use findViewById(R.id.nav_timetable); without it returning null and creating an NPE. What I'm trying to do is make the TimetableFragment get loaded by default and then people can use the drawer to go to other fragments.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    setupDrawer(toolbar);

    drawer.openDrawer(GravityCompat.START); // Opened this on first run so they know what's in it.

    // TODO Change this later to a home page and remove goToTimetablePage();
    // goToTimetablePage();
    goToFragmentFromNavMenuItem((MenuItem) findViewById(R.id.nav_timetable));
}

@Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
    // Handle navigation view item clicks here.

    goToFragmentFromNavMenuItem(menuItem);
    return true;
}

private void goToFragmentFromNavMenuItem(MenuItem menuItem) {
    // Create a new fragment and specify the fragment to show based on nav item clicked
    Fragment fragment = null;
    Class fragmentClass;

    switch (menuItem.getItemId()) {
        case R.id.nav_slideshow: // TODO fragmentClass = SlideshowFragment.class; break;
        case R.id.nav_share: // TODO fragmentClass = ShareFragment.class; break;
        case R.id.nav_timetable:
            fragmentClass = TimetableFragment.class;
            // goToTimetablePage();
            break;
        default:
            fragmentClass = TimetableFragment.class;
            // TODO Change this later to a home page:
            // TODO fragmentClass = HomeFragment.class; Remove "goToTimetablePage();"
    }


    //noinspection TryWithIdenticalCatches
    try {
        fragment = (Fragment) fragmentClass.newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }

    FragmentManager fragmentManager = getSupportFragmentManager();

    fragmentManager.beginTransaction().replace(R.id.flContent, fragment).commit();

    menuItem.setChecked(true);
    setTitle(menuItem.getTitle());
    drawer.closeDrawer(GravityCompat.START);
}

Error message:

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.gmail.gogobebe2.thedayahead/com.gmail.gogobebe2.thedayahead.MainActivity}: java.lang.NullPointerException: Attempt to invoke interface method 'int android.view.MenuItem.getItemId()' on a null object reference
  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
  at android.app.ActivityThread.-wrap11(ActivityThread.java)
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
  at android.os.Handler.dispatchMessage(Handler.java:102)
  at android.os.Looper.loop(Looper.java:148)
  at android.app.ActivityThread.main(ActivityThread.java:5417)
  at java.lang.reflect.Method.invoke(Native Method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
  Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'int android.view.MenuItem.getItemId()' on a null object reference
  at com.gmail.gogobebe2.thedayahead.MainActivity.goToNavFragment(MainActivity.java:123)
  at com.gmail.gogobebe2.thedayahead.MainActivity.onCreate(MainActivity.java:59)
  at android.app.Activity.performCreate(Activity.java:6237)
  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) 
  at android.app.ActivityThread.-wrap11(ActivityThread.java) 
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) 
  at android.os.Handler.dispatchMessage(Handler.java:102) 
  at android.os.Looper.loop(Looper.java:148) 
  at android.app.ActivityThread.main(ActivityThread.java:5417) 
  at java.lang.reflect.Method.invoke(Native Method) 
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 

I tried using the onCreateView method but it just made stackoverflow messages for some reason.

EDIT: Here's all the code to clear up any confusion: https://github.com/gogobebe2/TheDayAhead

EDIT2: Make sure you don't worry about the goToTimetablePage() method. It's unused at the moment and I plan on moving it to the TimetableFragment fragment.

EDIT3: I changed the method name to goToFragmentFromNavMenuItem() so that it better describes it's actions to clear things up. I also want to let you guys know that the onNavigationItemSelected(MenuItem menuItem) method works perfectly with the goToFragmentFromNavMenuItem() method, it's just that it doesn't work when used inside onCreate(Bundle savedInstanceState) because of the findViewById(R.id.nav_timetable) returning null all the time.

Also, this is my first Android app I've ever made and I'm kinda learning as I make it. I didn't even know what fragments were last night so I'm really new to Android, please don't assume any knowledge.


Solution

  • To avoid the crash due to the NPE you can test the returned value to be different by null before passing it:

    MenuItem menuItem = (MenuItem) findViewById(R.id.nav_timetable);
    if (menuItem != null) {
        goToNavFragment(menuItem);
    }
    

    Anyway, that will not solve your problem, in fact findViewById(int) is returning null because the view ID you're searching for doesn't exist in your Activity layout.

    The method findViewById(), which comes from the View class and you're invoking it on this which is an Activity (a Class that extends View), searches for a view id (from R) inside the currently inflated layout.

    Now, if you take a look to your onCreate implementation you see that you're calling the setContentViewMethod(R.layout.activity_main); this means that you're setting the layout of this activity taking it from the xml layout with id activity_main.

    The big problem here is that the layout you're passing (activity_main) does not contain the id you're looking for. Just take a look at the activity_main.xml layout file, as you can see there's no object with id R.id.nav_timetable.

    An Activity can only have one layout activity file loaded at once. You can still load other layout files for menus or particular view objects (Spinners, Lists, etc).

    The most important thing here is that you're trying to retrieve a MenuItem; to achieve this you have to override two methods onCreateOptionsMenu() and onOptionsItemSelected().


    The onCreateOptionsMenu() does what its name says. It creates the options menu you're going to use. Here you can hook your actions but the most important thing you MUST do is to inflate a menu layout. Without this you'll not be able to apply a custom menu layout and to use findViewById.

    Hence, before invoking the findViewById() you should do like:

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.your_menu_id, menu);
    
        // findViewById()
    
        return true;
    }
    

    The onOptionsItemSelected() will be called when an item from your previously created menu is selected, it will give you as an argument the selected MenuItem and here you can implement the menu item click actions. A standard implementation can be the following:

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_one:
                doSomething()
                return true;
            case R.id.action_two:
                doSomethingElse();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }