NullPointerException when changing layout properties from a custom view

Issue

I am trying to implement SearchView with animation,

SearchView is used in an activity_location.xml

cl_toolbar in activity_location overlaps the SearchView

hence I tried to change its visibility to INVISIBLE when openSearch() called and again VISIBLE when closeSearch() called.

getting a NullPointerException error in SearchView when tried to implement above things.

FATAL EXCEPTION: main                                                                                              Process: com.virusnetic.meather, PID: 11427                                                                                     java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.constraintlayout.widget.ConstraintLayout.setVisibility(int)' on a null object reference                                                                      at com.virusnetic.meather.views.SearchView._init_$lambda$0(SearchView.kt:26)
    at com.virusnetic.meather.views.SearchView.$r8$lambda$Mcmg8MRJAUGTfnVTINGMDsQmb5g(Unknown Source:0)
    at com.virusnetic.meather.views.SearchView$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
    at android.view.View.performClick(View.java:7441)
    at android.view.View.performClickInternal(View.java:7418)
    at android.view.View.access$3700(View.java:835)
    at android.view.View$PerformClick.run(View.java:28676)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:201)
    at android.os.Looper.loop(Looper.java:288)
    at android.app.ActivityThread.main(ActivityThread.java:7839)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

SearchView.kt

package com.virusnetic.meather.views

import android.animation.Animator
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewAnimationUtils
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.RelativeLayout
import androidx.constraintlayout.widget.ConstraintLayout
import com.virusnetic.meather.R

class SearchView(
    context: Context,
    attrs: AttributeSet
) : FrameLayout(context, attrs) {

init {
    LayoutInflater.from(context)
        .inflate(R.layout.view_search, this, true)

    findViewById<View>(R.id.open_search_button).setOnClickListener {
        // Set Location toolbar to Invisible
        findViewById<ConstraintLayout>(R.id.cl_ToolBar).visibility = View.INVISIBLE
        openSearch()
    }
    findViewById<View>(R.id.close_search_button).setOnClickListener {
        closeSearch()
        // Set Location toolbar to GONE
        findViewById<ConstraintLayout>(R.id.cl_ToolBar).visibility = View.VISIBLE
    }
}

private fun openSearch() {
    val open_search_button: View = findViewById(R.id.open_search_button)
    val search_input_text: EditText = findViewById(R.id.search_input_text)
    val search_open_view: RelativeLayout = findViewById(R.id.search_open_view)

    search_input_text.setText("")
    search_open_view.visibility = View.VISIBLE
    val circularReveal = ViewAnimationUtils.createCircularReveal(
        search_open_view,
        (open_search_button.right + open_search_button.left) / 2,
        (open_search_button.top + open_search_button.bottom) / 2,
        0f, width.toFloat()
    )
    circularReveal.duration = 300
    circularReveal.start()
}

private fun closeSearch() {
    val open_search_button: View = findViewById(R.id.open_search_button)
    val search_input_text: EditText = findViewById(R.id.search_input_text)
    val search_open_view: RelativeLayout = findViewById(R.id.search_open_view)

    val circularConceal = ViewAnimationUtils.createCircularReveal(
        search_open_view,
        (open_search_button.right + open_search_button.left) / 2,
        (open_search_button.top + open_search_button.bottom) / 2,
        width.toFloat(), 0f
    )

    circularConceal.duration = 300
    circularConceal.start()
    circularConceal.addListener(object : Animator.AnimatorListener {
        override fun onAnimationRepeat(animation: Animator?) = Unit
        override fun onAnimationCancel(animation: Animator?) = Unit
        override fun onAnimationStart(animation: Animator?) = Unit
        override fun onAnimationEnd(animation: Animator?) {
            search_open_view.visibility = View.INVISIBLE
            search_input_text.setText("")
            circularConceal.removeAllListeners()
        }
    })

}
}

also the layout where I used the view

view_search.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    android:id="@+id/frame"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="?actionBarSize"
    android:background="@color/background">

    <RelativeLayout
        android:id="@+id/search_closed_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:visibility="visible"
        android:background="@color/background">

        <View
            android:id="@+id/open_search_button"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:layout_marginEnd="16dp"
            android:background="@drawable/search"
            android:backgroundTint="@color/translucent"/>

    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/search_open_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center"
        android:layout_margin="4dp"
        android:background="@drawable/rounded_corner_background"
        android:visibility="invisible">

        <View
            android:id="@+id/close_search_button"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:layout_alignParentStart="true"
            android:layout_centerVertical="true"
            android:layout_marginStart="16dp"
            android:background="@drawable/ic_round_close_24"
            android:backgroundTint="@color/translucent"/>

        <EditText
            android:id="@+id/search_input_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:layout_toStartOf="@id/execute_search_button"
            android:layout_toEndOf="@id/close_search_button" />

        <!-- TODO add add button -->
        <View
            android:id="@+id/execute_search_button"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:layout_marginEnd="16dp"
            android:background="@drawable/add"
            android:backgroundTint="@color/translucent"/>

    </RelativeLayout>

</FrameLayout>

activity_location.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/background"
    android:padding="30dp"
    tools:context=".activities.LocationsActivity">
    
    <!-- Toolbar -->
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/cl_ToolBar"
        android:layout_width="match_parent"
        android:layout_height="?actionBarSize"
        android:elevation="1dp"
        android:layout_marginTop="20dp"
        app:layout_constraintTop_toTopOf="parent">

        <LinearLayout
            android:id="@+id/ll_back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/backicon" />
            <TextView
                android:id="@+id/tv_CurrentLocation"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="5dp"
                android:text="Select City"
                android:textColor="@color/translucent"
                android:textSize="18sp"
                android:fontFamily="@font/ubuntu_condensed_regular" />

        </LinearLayout>

        <ImageButton
            android:id="@+id/ib_currentLocations"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:layout_marginEnd="60dp"
            android:background="@drawable/current_location"
            android:backgroundTint="@color/translucent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

    <!-- Search View -->
    <com.virusnetic.meather.views.SearchView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        app:layout_constraintTop_toTopOf="parent"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_locations"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:listitem="@layout/location_list_item"
        tools:itemCount="3"
        app:layout_constraintTop_toBottomOf="@id/cl_ToolBar" />

</androidx.constraintlayout.widget.ConstraintLayout>

Solution

You will have to provide a way how your search view can access it. As you used it, it tried to find the cl_Toolbar in the SearchView – View can’t find view that is higher in the hierarchy*.

class SearchView(
    context: Context,
    attrs: AttributeSet
) : FrameLayout(context, attrs) {

    private var toolbarVisibilityHandler? = null

    init {
        LayoutInflater.from(context).inflate(R.layout.view_search, this, true)

        findViewById<View>(R.id.open_search_button).setOnClickListener {
            // Set Location toolbar to Invisible
            // findViewById<ConstraintLayout>(R.id.cl_ToolBar).visibility = View.INVISIBLE
            toolbarVisibilityHandler?.hideToolbar()
            openSearch()
        }
    
        findViewById<View>(R.id.close_search_button).setOnClickListener {
            closeSearch()
            // Set Location toolbar to GONE
            // findViewById<ConstraintLayout>(R.id.cl_ToolBar).visibility = View.VISIBLE
            toolbarVisibilityHandler?.showToolbar()
        }
    }

    fun setToolbarVisibilityHandler(handler: ToolbarVisibilityHandler?) {
        toolbarVisibilityHandler = handler
    }
    ...
}

I would suggest that you create some interface for callbacks that show/hide the Toolbar.

interface ToolbarVisibilityHandler {

    fun showToolbar()

    fun hideToolbar()
}

Then in the place where you initiate the views you set the handler in SearchView.

<!-- Search View -->
<com.virusnetic.meather.views.SearchView
    android:id="@+id/searchView"
    .../>

override fun onCreate() {
    ...
    val toolbarView = findViewById<ConstraintLayout>(R.id.cl_ToolBar)
    findViewById<SearchView>(R.id.searchView).setToolbarVisibilityHandler(object: ToolbarVisibilityHandler {

        override fun showToolbar() {
            toolbarView.visibility = View.VISIBLE
        }

        override fun hideToolbar() {
            toolbarView.visibility = View.INVISIBLE
        }

    })
}

Answered By – TheLibrarian

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published