Нулевой указатель внутри фрагмента с использованием синтетического - PullRequest
0 голосов
/ 24 декабря 2018

Я получаю нулевые указатели (иногда) на представления внутри фрагментов, используя синтетические.Я не знаю, что не так, потому что я объявляю фрагменты в XML, и я думаю, что, когда я вызываю метод populate из Activity, корневое представление уже создано

В любом случае, я не верю, чтоправильно проверяйте это каждый раз, когда я вызываю заполнение ...

Я пишу пример кода того, что происходит (нулевой указатель будет на tv):

Фрагмент:

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_foo.*

class FooFragment : Fragment() {

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

    fun populate(fooName: String) {
        tv.text = fooName
    }
}

И XML, связанный в XML с Activity, связанной

<fragment
android:id="@+id/fFoo"
android:name="com.foo.FooFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@layout/fragment_foo"/>

Это будет относиться к Activity:

class FooActivity : AppCompatActivity() {
    private lateinit var fooFragment: FooFragment

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_foo)
        fooFragment = fFoo as FooFragment
    }

    private fun loadDataProductionInfo(productionId: Long) {
        FooAPIParser().getFoo {
            initFoo(it.fooName)
        }
    }

    private fun initFoo(fooName: String) {
        fooFragment.populate(fooName)
    }
}

И, наконец, XML конкретного фрагмента:

 <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="wrap_content">

        <TextView
            android:id="@+id/tvFoo"
            style="@style/Tv"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Ответы [ 3 ]

0 голосов
/ 24 декабря 2018

Подумав в ваших ответах, я внедрил патч.Мне не очень нравится, но я думаю, что это должно работать лучше, чем сейчас.Как вы думаете?

Я бы расширил каждый фрагмент, который я хочу проверить из этого класса:

import android.os.Bundle
import android.os.Handler
import android.view.View
import androidx.fragment.app.Fragment

open class BaseFooFragment : Fragment() {

    private var viewCreated = false
    private var attempt = 0

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewCreated = true
    }

    fun checkViewCreated(success: () -> Unit) {
        if (viewCreated) {
            success()
        } else {
            initLoop(success)
        }
    }

    private fun initLoop(success: () -> Unit) {
        attempt++
        Handler().postDelayed({
            if (viewCreated) {
                success()
            } else {
                if (attempt > 3) {
                    return@postDelayed
                }
                checkViewCreated(success)
            }
        }, 500)
    }
}

Вызов во фрагменте будет более или менее чистым:

fun populate(fooName: String) {
    checkViewCreated {
        tv.text = fooName
    }
}
0 голосов
/ 27 декабря 2018

Наконец, я получил помощь, чтобы найти лучший ответ.

open class BaseFooFragment : Fragment() {

    private var listener: VoidListener? = null
    private var viewCreated = false

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewCreated = true
        listener?.let {
            it.invoke()
            listener = null
        }
    }

    override fun onDestroyView() {
        viewCreated = false
        super.onDestroyView()
    }

    fun doWhenViewCreated(success: VoidListener) {
        if (viewCreated) {
            success()
        } else {
            listener = success
        }
    }
}

VoidListener просто так:

typealias VoidListener = () -> Unit

Один из способов сделать это более общий (например, если вы хотите использовать более одного слушателя) может выглядеть так:

open class BaseFooFragment : Fragment() {

    private val listeners: MutableList<VoidListener> = mutableListOf()
    private var viewCreated = false

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewCreated = true
        listeners.forEach { it() }
        listeners.clear()
    }

    override fun onDestroyView() {
        viewCreated = false
        super.onDestroyView()
    }

    fun doWhenViewCreated(success: VoidListener) {
        if (viewCreated) {
            success()
        } else {
            listeners.add(success)
        }
    }
}
0 голосов
/ 24 декабря 2018

Синтетические свойства Kotlin не волшебны и работают очень просто.Когда вы обращаетесь к btn_K, он вызывает getView().findViewById(R.id.tv)

Проблема в том, что вы обращаетесь к нему слишком рано getView() возвращает null в onCreateView.Попробуйте сделать это методом onViewCreated:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    //Here
    }
}

Использование представлений вне onViewCreated.

tv?.text = "kotlin safe call"

Если вы собираетесь использовать Fragments, вам нужнопродлить FragmentActivity

...