NullPointerException podczas próby uzyskania dostępu do widoków we fragmencie Kotlin


240

Jak korzystać z rozszerzeń systemu Android Kotlin za pomocą Fragments? Jeśli użyję ich w środku onCreateView(), otrzymam ten NullPointerExceptionwyjątek:

Przyczyna: java.lang.NullPointerException: Próba wywołania metody wirtualnej „android.view.View android.view.View.findViewById (int)” w odwołaniu do obiektu o wartości null

Oto kod fragmentu:

package com.obaied.testrun.Fragment

import android.os.Bundle
import android.support.v4.app.Fragment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.obaied.acaan.R
import kotlinx.android.synthetic.main.fragment_card_selector.*

public class CardSelectorFragment : Fragment() {
    val TAG = javaClass.canonicalName

    companion object {
        fun newInstance(): CardSelectorFragment {
            return CardSelectorFragment()
        }
    }

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        var rootView = inflater?.inflate(R.layout.fragment_card_selector, container, false)
        btn_K.setOnClickListener { Log.d(TAG, "onViewCreated(): hello world"); }

        return rootView
    }
}
`

4
Jeśli chcesz to zrobić w onCreateView, btn_K będzie również własnością rootView. Mógłbyś zrobićrootView.btn_K.setOnClickListener
Makotosan

Dzięki @Makotosan twoja odpowiedź zadziałała dla mnie.
Mahdi Bagjani,

Oczyszczenie, przebudowanie i ponowne uruchomienie studia Android działało dla mnie
Otziii

@Otziii Wątek został napisany po raz pierwszy w 2015 roku. Pierwsza odpowiedź ma 259 głosów i została zaakceptowana. Nie uważam za konieczne dodawanie kolejnych odpowiedzi.
solidak

2
@Solidak Miałem ostatnio ten problem, wypróbowałem wszystkie odpowiedzi i jedyne, co sprawiło, że zadziałało, to to, co teraz skomentowałem. Miałem odpowiedź na ten wątek, ale został on poddany ocenie, więc zmieniłem go na komentarz. Wygląda na to, że ludzie nadal mają ten problem i nikt nie wspomniał o jego czyszczeniu i ponownym uruchomieniu.
Otziii

Odpowiedzi:


443

Syntetyczne właściwości Kotlin nie są magiczne i działają w bardzo prosty sposób. Gdy uzyskujesz dostęp btn_K, wymaga getView().findViewById(R.id.btn_K).

Problem polega na tym, że uzyskujesz do niego dostęp zbyt wcześnie. getView()powraca nullw onCreateView. Spróbuj to zrobić onViewCreatedmetodą:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    btn_K.setOnClickListener { Log.d(TAG, "onViewCreated(): hello world"); }
}

2
Zadziałało!! Dzięki. Tylko szybki heads-up do wykorzystania w przyszłości. Miałem inny wyjątek i kopałem trochę głębiej i okazało się, że wyjątek zerowy odniesienia przychodził z wywołania zwrotnego asynchronicznego do wątku interfejsu użytkownika, w którym próbowałby uzyskać dostęp do właściwości syntetycznej, ale w tym czasie był już zerowy. Upewnij się, że korzystasz z operatora Safe Call (?.) Lub innego operatora bezpieczeństwa o zerowej wartości. Pomogłoby to również zachować klasowe odniesienie do widoku i nie polegać na syntetycznych właściwościach pozaonViewCreated()
solidak

2
Jedno pytanie - generuje inny kod dla działania i fragmentu? Jeśli użyjemy innej struktury, która nie zawiera getView()lub nie może wywoływać findViewById(), czy istnieje sposób obejścia tego? Na przykład, naucz, która funkcja zwróci mój układ?
milosmns

7
Możesz również uzyskać do niego dostęp tak rootView.btn_K, jakbyś miał widok (i to nie tylko we fragmentach, można to zrobić wszędzie)
Adib Faramarzi

To działa ! Należy to jednak bardziej podkreślić w dokumentacji Kotlina. Nie zauważyłem tej metody aż do tego postu. Dzięki w każdym razie!
sokarcreative

2
Zawsze używałam go w onViewCreated, ale nadal na niektórych urządzeniach (dostałem raport od Crashlytics) miał wyjątek „nie może być zerowy”. Widok jest tam. Nadmuchuję prawidłowy układ, działa na moim urządzeniu. To po prostu dziwne, że nie działa na losowym urządzeniu.
Arie Agung

9

Dzwonisz na to btn_Kza wcześnie, ponieważ wtedy zwraca wartość zerową i daje ci wyjątek wskaźnika zerowego.

Możesz użyć tych widoków za pomocą tej syntetycznej wtyczki w onActivityCreated()metodzie, która jest nazywana tuż po onCreateView()cyklu życia Fragmentu.

onActivityCreated()
{
        super.onActivityCreated(savedInstanceState)
        btn_K.setOnClickListener{}
}

Chciałbym tylko zaznaczyć, że z niektórych powodów ta odpowiedź zadziałała dla mnie, a odpowiedź zaakceptowana nie. Moje widoki są puste, onViewCreatedale zostały zdefiniowane w onActivityCreated. Nie wiem jednak dlaczego.
NathanL

6

Właściwości syntetyczne wygenerowane przez wtyczkę Kotlin Android Rozszerzenia potrzebuje viewdo Fragment/Activitybyć ustawione przed ręką.

W twoim przypadku Fragmentmusisz użyć view.btn_KwonViewCreated

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    super.onCreateView(inflater, container, savedInstanceState)
    val view = inflater.inflate(R.layout.fragment_card_selector, container, false)
    view.btn_K.setOnClickListener{} // access with `view`
    return view
}

Lub lepiej, powinieneś mieć dostęp tylko do właściwości syntetycznych w onViewCreated

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    super.onCreateView(inflater, container, savedInstanceState)
    return inflater.inflate(R.layout.fragment_card_selector, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    btn_K.setOnClickListener{} // access without `view`
}

Zauważ, że savedInstanceStateparametr powinien mieć wartość zerową Bundle?, a także sprawdź Importowanie właściwości syntetycznych

Wygodnie jest zaimportować wszystkie właściwości widżetu dla określonego układu za jednym razem:

import kotlinx.android.synthetic.main.<layout>.*

Dlatego jeśli nazwa pliku układu to activity_main.xml, zaimportujemy kotlinx.android.synthetic.main.activity_main.*.

Jeśli chcemy wywołać właściwości syntetyczne w View, powinniśmy również zaimportować kotlinx.android.synthetic.main.activity_main.view.*.


3

jedyne, co musisz zrobić, to:

override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    var rootView = inflater?.inflate(R.layout.fragment_card_selector, container, false)
    rootView.btn_K.setOnClickListener { Log.d(TAG, "onViewCreated(): hello world"); }

    return rootView
}

1
Tak, po prostu użyj val view = inflater.inflate() view.button.text = "caption".
CoolMind

To najlepsza odpowiedź - na pewno najfajniejsze rozwiązanie!
CacheMeOutside

@CacheMeOutside nie, ponieważ wciąż jest to kod typu „kocioł” rootView.subView.doSomething. Lepiej jest używać widoków zaczynających się odonViewCreated
user924

3

nie ma potrzeby definiowania obiektu towarzyszącego, wystarczy wywołać każdy identyfikator w widoku takim jak

 lateinit var mView: View
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    mView=inflater.inflate(R.layout.product_list,container,false)

    mView.addProduct.setOnClickListener {

        val intent=Intent(activity,ProductAddActivity::class.java)
        startActivity(intent)
    }     return mView
}

1

Fragmenty napisz kod w onActivityCreated: -

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        super.onCreateView(inflater, container, savedInstanceState)
        return inflater.inflate(R.layout.login_activity, container, false)

    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        callbackManager = CallbackManager.Factory.create()
        initialization()
        onClickLogin()
        onClickForgot()
        onClickSocailLogIn()

  }

1
Czemu? Skąd ta wiedza? Dlaczego nie onViewCreatedzamiast tego?
kuzdu

0

W moim przypadku nic nie działało, dopóki nie zastosowałem się do rady Otziii w komentarzach. Wyczyść, przebuduj (nie wymaga restartu), uruchom ponownie aplikację. Ja też nie musiałem iść z tym onActivityCreatedi po prostu onCreateViewzrobiłem to.

Pewnego razu popełniłem również błąd nadmuchiwania złego układu, dlatego nie otrzymałem oczywiście oczekiwanych kontroli.


Widziałem to dzieje się onActivityCreatedzbyt
Jemshit Iskenderov

0

Dodając go do odpowiedzi @Egor Neliuba, Tak, za każdym razem, gdy wywołujesz widok bez odniesienia, kotlinex szuka rootView, a ponieważ jesteś wewnątrz fragmentu i fragment nie ma getView()metody. Dlatego może rzucićNullPointerException

Istnieją dwa sposoby na przezwyciężenie tego,

  • Albo przesłonisz, onViewCreated()jak wspomniano
  • Lub Jeśli chcesz powiązać widoki w innej klasie (powiedzmy anonimowy), możesz po prostu utworzyć funkcję rozszerzenia taką jak ta,

    fun View.bindViews(){...}

Drugie podejście jest pomocne, gdy masz jeden fragment z wieloma zachowaniami.


-2
class CardSelectorFragment : Fragment() {


val TAG = javaClass.canonicalName

companion object {
    fun newInstance(): CardSelectorFragment {
        return CardSelectorFragment()
    }
}

override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    var rootView = inflater?.inflate(R.layout.fragment_card_selector, container, false)

    rootView?.findViewById<TextView>(R.id.mTextView)?.setOnClickListener{
        Log.d(TAG, "onViewCreated(): hello world");
    }
    //btn_K.setOnClickListener { Log.d(TAG, "onViewCreated(): hello world"); }
    return rootView
}

}

** Tutaj używasz btn_K.setOnClickListener przed znalezieniem - Musisz znaleźć element xml w kodzie java / kotlin za pomocą findViewById wtedy i tylko wtedy możesz wykonać operację na tym widoku lub elemencie.

- Więc dlatego wykonałeś zerowy wskaźnik

**

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.