0

I'm trying to change the text of a NavigationView's child. I am using findViewById to get directly to the TextView. It works in the MainActivity, but in every other Activity findViewById returns a null reference.

By the way this is my first submitted question and I hope I respected the guidelines.

I can't seem to figure this out so I don't know where to start.

This is the NavigationView located in the layout of every activity

<android.support.design.widget.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_gravity="start"
       android:visibility="visible"
       app:headerLayout="@layout/nav_header_main"
       app:menu="@menu/activity_main_drawer"/>

nav_header_main is the component that has the said TextView, it is referenced in the code above. This is nav_header_main:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_header"
    android:theme="@style/ThemeOverlay.AppCompat.Dark">
    <TextView
        android:id="@+id/nav_header_textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="Angheluta Filip"
        android:textAppearance="@style/TextAppearance.AppCompat.Body1"
        android:textColor="@android:color/white" />
</LinearLayout>

This code is from MainActivity, where finding the said text view doesn't return a null reference

    NavigationManager navigationManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Downloads the database and than proceeds to setup the Activity
        //onDataChange gets called when the download is finished and dataSnapshot is the downloaded
        //data

        final AppCompatActivity context = this;
        FirebaseHandler.getFirebaseHandler().getReference().addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

                FirebaseHandler.getFirebaseHandler().setData(dataSnapshot);
                DataSnapshot userSnapshot = dataSnapshot.child("Users").child(FirebaseHandler.getFirebaseHandler().getAuth().getUid());
                User.initializeCurrentUser(userSnapshot);

                //Add side bar to this activity
                navigationManager = new NavigationManager(context);
                navigationManager.createNavBar();

                //Recycler view setup
                RecyclerManager recyclerManager = new RecyclerManager();
                recyclerManager.createRecyclerWithLargeElements(context, Voluntariat.getDataSet());

                //Sort spinner setup
                SortSpinnerManager sortSpinnerManager = new SortSpinnerManager();
                sortSpinnerManager.createSortSpinnerManager(context, recyclerManager);

                //Search bar setup
                SearchBarManager searchBarManager = new SearchBarManager();
                searchBarManager.createSearchBar(context, recyclerManager, Voluntariat.getDataSet());

                TextView textViewHeader = findViewById(R.id.nav_header_textView);
                Log.d("textHeader", textViewHeader.getText().toString());
                textViewHeader.setText(User.currentUser.lastName + " " + User.currentUser.firstName);
            }

            @Override
            public void onCancelled(@NonNull DatabaseError databaseError) {

            }
        });



    }

But this is from any other Activity in the onCreate method


    RecyclerManager recyclerManager;
    NavigationManager navigationManager;

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

        //Add side bar to this activity
        navigationManager = new NavigationManager(this);
        navigationManager.createNavBar();

        //RECYCLERVIEW SETUP
        recyclerManager = new RecyclerManager();
        recyclerManager.createRecyclerWithSmallElements(this, Voluntariat.getDataSet());

        //Search bar managing
        SearchBarManager searchBarManager = new SearchBarManager();
        searchBarManager.createSearchBar(this, recyclerManager, Voluntariat.getDataSet());

        TextView textViewHeader = findViewById(R.id.nav_header_textView);
        Log.d("textHeader", textViewHeader.getText().toString());
        textViewHeader.setText(User.currentUser.lastName + " " + User.currentUser.firstName);
    }

When I access any activity other than the MainActivity I get NullPointerException on the textViewHeader.

EDIT: I added more code and i changed it so the findViewById is called straight from the Activity file rather than from the NavigationManager file. The error is the same.

EDIT2: Added some more code from activity_toate_vol.xml:

It seems like this layout doesn't have any view with the problematic id, but the id is located in the nav_header_main.xml file which is set in the activity_toate_vol.xml file with the headerLayout attribute:

 app:headerLayout="@layout/nav_header_main"

This NavigationView is a direct child of the root layout and the rest of the code is irrelevant, This same navigation view is found in the MainActivity as well.

<android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:visibility="visible"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/activity_main_drawer">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="bottom"
            android:gravity="bottom"
            android:orientation="vertical"
            android:padding="16dp">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"
                android:gravity="bottom"
                android:orientation="horizontal">

                <ImageView
                    android:id="@+id/imgdeconctare"
                    android:layout_width="27dp"
                    android:layout_height="27dp"
                    android:alpha=".6"
                    android:src="@drawable/ic_logout"
                    app:srcCompat="@drawable/ic_logout"
                    tools:srcCompat="@drawable/ic_logout" />

                <TextView
                    android:id="@+id/logout"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_marginLeft="20dp"
                    android:gravity="center|start"
                    android:text="Deconectare"
                    android:textColor="@android:color/black" />
            </LinearLayout>

        </LinearLayout>

    </android.support.design.widget.NavigationView>

EDIT 3: I came out with a workaround and also identified the problem. Turns out I have to somehow wait for that NavigationView to inflate before I update it's text. Can anyone help me with the proper way to do this? Cause I am not happy with this.

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    wait(150);
                }
                catch(Exception e){
                }
                TextView textViewHeader = findViewById(R.id.nav_header_textView);
                Log.d("textHeader", textViewHeader.getText().toString());
                textViewHeader.setText(User.currentUser.lastName + " " + User.currentUser.firstName);
            }
        }, 100);
  • Please provide the surrounding method and class definitions for each code snippet in order to provide more context. – Code-Apprentice Jun 10 '19 at 16:36
  • Ok, I was worried I would paste too much code if I do that but here we go – Filip Angheluta Jun 10 '19 at 16:38
  • You misunderstand. I'm **not** asking you to post your entire activities. I'm asking for you to use valid syntax by adding `class MyActivity {}` and `void myMethod() {}`around the current code snippets. If you add more than 4 or 5 lines to your current code, you are adding too much. – Code-Apprentice Jun 10 '19 at 16:39
  • I can't test this right now but I have a feeling you are getting a null pointer exception because of the context you are using. I could be wrong be it seems strange to cast your MainActivity's context to AppCompatActivity instead of just passing the context of the MainActivity – Chris K Jun 10 '19 at 16:48
  • @ck1221 But the thing is, I only cast the context to AppCompatActivity in the MainActivity which works fine – Filip Angheluta Jun 10 '19 at 16:51
  • I'm currently not on my dev pc, but I can definitely recommend looking at Kotlin for this. Kotlin has an option to just directly reference an id specified in xml, without having the need to create an object (using the findviewbyid function). I believe it's called synthetic binding. Can definitely recommend. Read more about this here: https://kotlinlang.org/docs/tutorials/android-plugin.html I know this doesn't really solve your issue, but I just wanted to point out one of the major improvements Kotlin has over Java (especially in Android Development). – LoopsGod Jun 10 '19 at 16:58

1 Answers1

0

Welcome! When findViewById returns null, that can mean 2 things.

1) The layout that you pass to 'setContentView' does not have a view with that id...

2) The layout does have that view, but the view itself is not fully inflated yet. To wait until a view is fully inflated, you can use a GlobalLayoutListener

in java:

setContentView(R.layout.activity_other);
final NavigationView navigationView = findViewById(R.id.nav_view);

navigationView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        TextView text = navigationView.findViewById(R.id.nav_header_textView);
        text.setText("Hello StackOverflow");
  navigationView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
});

and when you switch to kotlin:

setContentView(R.layout.activity_main)
val view = findViewById<NavigationView>(R.id.nav_view)
view?.viewTreeObserver?.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener{
    override fun onGlobalLayout() {
        val header = findViewById<TextView>(R.id.nav_header_textView)
        header.text= "Hallo Main"
        view.viewTreeObserver.removeOnGlobalLayoutListener(this)
    }
})
Tamir Abutbul
  • 7,301
  • 7
  • 25
  • 53
Entreco
  • 12,738
  • 8
  • 75
  • 95
  • Well, the view that has that id is in the nav_header_main.xml file, which is included in the activity_toate_vol.xml via the headerLayout attribute of the NavigationView. I can't figure out why it works in the MainActivity, but not in any activity i navigate to through the MainActivity, anytime I access any other activity it crashes with NullPointerException at the line where I Log the content of the TextView i'm trying to access. – Filip Angheluta Jun 10 '19 at 18:21
  • How do you know what the id is, if you only have `app:headerLayout="@layout/nav_header_main"`. – Entreco Jun 10 '19 at 18:34
  • It is in the nav_header_main.xml file. I seem to have identified the problem though, can you check my edit please and weigh in? – Filip Angheluta Jun 10 '19 at 18:49
  • Thank you very much! The GlobalLayoutListener trick worked perfectly. – Filip Angheluta Jun 10 '19 at 20:46