我正在使用Kotlin合成绑定,RxJava,Retrofit和MVP模式开发一个Android应用程序。在此应用程序上,有一个屏幕,该屏幕应该从API提取一些数据并用返回的数据填充RecyclerView
。
所有这些操作每次都可以正常工作,直到我尝试旋转屏幕为止……重新创建“活动”和“片段”并尝试访问RecyclerView后,应用程序崩溃,但出现以下异常:
10-25 18:20:00.368 4580-4580/com.sample.app E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.sample.app, PID: 4580
java.lang.IllegalStateException: recyclerView must not be null
at com.sample.app.features.atendimento.ClientesAtendimentosFragment.onAtendimentosLoaded(ClientesAtendimentosFragment.kt:56)
at com.sample.app.features.atendimento.ClienteAtendimentoActivity.showClientesAtendimento(ClienteAtendimentoActivity.kt:126)
at com.sample.app.features.atendimento.ClienteAtendimentoPresenter$getClientesAtendimento$3.accept(ClienteAtendimentoPresenter.kt:38)
at com.sample.app.features.atendimento.ClienteAtendimentoPresenter$getClientesAtendimento$3.accept(ClienteAtendimentoPresenter.kt:18)
at io.reactivex.internal.observers.ConsumerSingleObserver.onSuccess(ConsumerSingleObserver.java:63)
at io.reactivex.internal.operators.single.SingleDoAfterTerminate$DoAfterTerminateObserver.onSuccess(SingleDoAfterTerminate.java:71)
当我尝试调用此方法时会发生这种情况:
override fun onAtendimentosLoaded(atendimentos: List<UsuarioAtendimento>) {
recyclerView.visibility = View.VISIBLE
recyclerView.adapter = ClienteAtendimentoAdapter(atendimentos, this)
}
由于某种原因,recyclerView变量为null,即使Activity和Fragment都已成功创建并且已经在电话上可见(崩溃前我什至可以看到加载屏幕),应用也会崩溃。 / p>
那么,即使Activity和Fragment已经可见,为什么我的视图仍为空?
编辑:我还要添加MVP文件,以帮助了解此处发生的情况。
演示者
class ClienteAtendimentoPresenter(private val repository: ClienteAtendimentoRepository): ClienteAtendimentoContract.Presenter {
private var view: ClienteAtendimentoContract.View? = null
private var disposable: Disposable? = null
override fun getClientesAtendimento() {
try {
disposable = repository.getClientesEmAtendimento()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
view?.hideErrors()
view?.showLoading()
}
.doAfterTerminate {
view?.hideLoading()
}
.subscribe(
{
view?.showClientesAtendimento(it)
},
{
handleError(it)
}
)
} catch (e: NoConnectionException) {
view?.hideLoading()
handleError(e)
}
}
override fun stop() {
view = null
disposable?.dispose()
}
override fun attachView(view: BaseView<BasePresenter>) {
if (view is ClienteAtendimentoContract.View)
this.view = view
}
}
查看
class ClienteAtendimentoActivity : BaseActivity(), ClienteAtendimentoContract.View {
override val presenter: ClienteAtendimentoContract.Presenter by inject()
private lateinit var mSectionsPagerAdapter: SectionsPagerAdapter
inner class SectionsPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
private val fragAtendimento = ClientesAtendimentosFragment.newInstance()
private val fragEspera = ClientesAtendimentosFragment.newInstance()
override fun getItem(position: Int): Fragment {
return if (position == 0) fragAtendimento else fragEspera
}
override fun getCount(): Int = 2
}
private fun setupTabs() {
mSectionsPagerAdapter = SectionsPagerAdapter(supportFragmentManager)
// Set up the ViewPager with the sections adapter.
container.adapter = mSectionsPagerAdapter
container.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs))
tabs.addOnTabSelectedListener(TabLayout.ViewPagerOnTabSelectedListener(container))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cliente_atendimento)
// ...
setupTabs()
}
override fun onResume() {
super.onResume()
presenter.attachView(this)
presenter.getClientesAtendimento()
}
override fun onPause() {
super.onPause()
presenter.stop()
}
// ...
override fun showClientesAtendimento(atendimentos: AtendimentosLista) {
val atendimentosFrag = mSectionsPagerAdapter.getItem(0) as ClientesAtendimentosFragment
val filaEsperaFrag = mSectionsPagerAdapter.getItem(1) as ClientesAtendimentosFragment
atendimentosFrag.onAtendimentosLoaded(atendimentos.atendimentos) // This is where I'm getting the error.
filaEsperaFrag.onAtendimentosLoaded(atendimentos.espera)
}
// ...
}
片段
class ClientesAtendimentosFragment : Fragment(), AtendimentosFragmentCallback, OnClickAtendimentoCallback {
private lateinit var recyclerView: RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.fragment_single_rv, container, false)
recyclerView = rootView.findViewById(R.id.recyclerView) // I did this to test if Kotlin synthetic binding was the problem.
return rootView
}
// ...
override fun onAtendimentosLoaded(atendimentos: List<UsuarioAtendimento>) {
recyclerView.visibility = View.VISIBLE // App is crashing here.
recyclerView.adapter = ClienteAtendimentoAdapter(atendimentos, this)
}
// ...
}
如this answer上的建议,我试图使该片段保留该实例,但这没有用。
我还尝试抛弃Kotlin合成绑定,并使recyclerView变量成为lateinit var
方法内的onCreateView()
变量,例如以下代码段:
private lateinit var recyclerView: RecyclerView
//...
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.fragment_single_rv, container, false)
recyclerView = rootView.findViewById(R.id.recyclerView)
return rootView
}
但是现在出现以下错误:
10-25 18:32:42.261 10572-10572/com.sample.app E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.sample.app, PID: 10572
io.reactivex.exceptions.UndeliverableException: kotlin.UninitializedPropertyAccessException: lateinit property recyclerView has not been initialized
at io.reactivex.plugins.RxJavaPlugins.onError(RxJavaPlugins.java:367)
at io.reactivex.internal.observers.ConsumerSingleObserver.onSuccess(ConsumerSingleObserver.java:66)
at io.reactivex.internal.operators.single.SingleDoAfterTerminate$DoAfterTerminateObserver.onSuccess(SingleDoAfterTerminate.java:71)
at io.reactivex.internal.operators.single.SingleDoOnSubscribe$DoOnSubscribeSingleObserver.onSuccess(SingleDoOnSubscribe.java:77)
at io.reactivex.internal.operators.single.SingleObserveOn$ObserveOnSingleObserver.run(SingleObserveOn.java:81)
at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:119)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6939)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property recyclerView has not been initialized
at com.sample.app.features.atendimento.ClientesAtendimentosFragment.onAtendimentosLoaded(ClientesAtendimentosFragment.kt:59)
编辑:我做了Xite suggested on his answer,然后发现由于某种原因,当我旋转屏幕时,新的Rx调用仍在尝试使用旧的Fragment引用,即使已经成功创建并显示了新内容。我不确定应该在哪里清理它,或者为什么要这样做,也许与supportFragmentManager
有关?
答案 0 :(得分:1)
首先,尝试对其进行调试,然后在旋转之前和旋转之后在this
内查找片段(onAtendimentosLoaded()
)的哈希码。假设您没有使用setRetainInstance
,它们应该有所不同。如果它们相同,那么您将无法正确清除某些内容,很难说出到底是什么原因造成的,因为我们没有相关的片段,活动和演示者代码。
答案 1 :(得分:1)
我认为问题出在您的SectionsPagerAdapter.getItem
中,您应该始终实例化一个新片段:
override fun getItem(position: Int): Fragment {
return if (position == 0) ClientesAtendimentosFragment.newInstance() else ClientesAtendimentosFragment.newInstance()
}
我知道FragmentPagerAdapter
的文档尚不清楚,但是如果您查看其instantiateItem
方法:
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position); // @Mauker it requires a new fragment instance!
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
很明显,每当调用SectionsPagerAdapter.getItem
时,我们都需要提供一个新的片段实例。因此,在您的情况下,当屏幕旋转时,适配器(假设适配器已保留)将返回片段的旧引用,这些片段的视图可能已被破坏。
然后您不应该调用getItem
,它只能由适配器使用,它只会在您调用它时不断返回新的片段实例。
如果您确实需要获取参考,请尝试以下操作:
val fragments = SparseArray<Fragment>()
fun getFragment(position: Int): Fragment? { // call this instead
return fragments.get(position)
}
override fun getItem(position: Int): Fragment {
val fragment = SomeFragment()
fragments.put(position, fragment) // store it for later use
return fragment
}
我希望它能起作用,祝你好运!
答案 2 :(得分:0)
我经常遇到这个问题。我只是每次都使用Kotlin的null安全功能来解决此问题。
override fun onAtendimentosLoaded(atendimentos: List<UsuarioAtendimento>) {
recyclerView?.visibility = View.VISIBLE
recyclerView?.adapter = ClienteAtendimentoAdapter(atendimentos, this)
}
在recyclerView
答案 3 :(得分:0)
经过一番挖掘并在这里找到了一些答案的帮助下,我发现this post on Medium促使我找到了解决方法。
是的,问题出在FragmentPagerAdapter
上,它返回一个旧的片段实例,该实例导致空指针。
要解决此问题,首先我将创建片段的方式更改为:
inner class SectionsPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
private val fragList = ArrayList<Fragment>()
fun addFragment(frag: Fragment) {
fragList.add(frag)
}
override fun getItem(position: Int): Fragment {
return fragList[position]
}
override fun getCount(): Int = fragList.size
}
然后,我必须重写适配器中的instantiateItem(container: ViewGroup, position: Int)
方法,并使它像这样:
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val ret = super.instantiateItem(container, position)
fragList[position] = ret as Fragment
return ret
}
至于为什么发生这种情况,this answer on SO对此进行了很好的解释。