web-dev-qa-db-ja.com

フラグメントはViewModelから値を取得しません

Jetpackナビゲーションを備えた単一のアクティビティアプリケーションがあります。多くのフラグメントですべてのアプリケーションのオブジェクト変数が必要です。したがって、私はViewModelを使用して、ViewModelを提供する親フラグメントクラスを作成しました。

class MyViewModel : ViewModel() {
    var myData : CustomClass? = null
    ...
}

open class ParentFragment : Fragment {
    val model : MyViewModel by activityViewModels()
    lateinit var myData : CustomClass

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        model.myData?.let {
            myData = it
        }
    }
}

myDataは、ParentFragmentを使用する場合はnullにしないでください。ただし、ランダムにkotlin.UninitializedPropertyAccessException: lateinit property myData has not been initializedを使用する場合myData

ViewModelがmyDataを保持しない可能性はありますか?プロパティが初期化されたことをどのように確認できますか?

更新:1を試す

私はParentFragmentでこのコードを試しました:

open class ParentFragment : Fragment {
    val model : MyViewModel by activityViewModels()
    lateinit var backingData : CustomClass
    val myData : CustomClass
        get() {
            if (!::backingData.isInitialized)
                model.getData()?.let {
                    backingData = it
                }
            return backingData
        }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        model.getData?.let {
            backingData = it
        }
    }
}

しかし、myDataを呼び出しても問題は解消されません。ViewModelデータが失われたようです

PDATE 2:コードの詳細

fragmentを拡張するParentFragmentに入る前に、データをViewModelに設定してから、次のfragmentに移動します。

// Inside FirstFragment
if (myData != null) {
    model.setData(myData)
    findNavController().navigate(FirstFragmentDirections.actionFirstToNextFragment())
}

データが設定される前に私のNavControllerがナビゲーションを行うことは可能ですか?

編集3:カスタムアプリケーションクラスを使用してみます

以下の回答によると、私はcustom Application class、そして私はこのクラスを通してオブジェクトを渡そうとしました:

class MyApplication: Application() {

    companion object {
        var myObject: CustomClass? = null
    }
}

しかし、残念ながら私には何の変化もありません。多分私のオブジェクトは大きすぎて正しく割り当てられませんか?

2
Ady

つまり、データをオブジェクト変数として参照していて、ViewModelを使用することを選択したときにいつでもアクセスできるようにしています。私には、あなたは自分の選択肢を考えすぎたように思えます。

提案

オブジェクトのライフサイクルは手動で管理されているようです。したがって、静的変数を使用する必要があります。これは、(コンパニオン)オブジェクト内のプロパティとしてKotlinに変換されます。マニフェスト内でカスタムアプリケーションクラスを宣言し、そのonCreateメソッドでオブジェクトを割り当てて、このクラスのコンパニオンオブジェクトに配置することをお勧めします。もちろん、後でいつでも割り当てることができます。これにより、次のようになります。

  • コード内の_YourApplication.mData_を介していつでもアクセスできます。
  • JVM外の実装に依存するオブジェクトは適切に管理できます。例:すでにポートにバインドしている場合、後続の呼び出しでこれを行うことはできません-たとえば、viewModelがその状態を復元するとき。おそらく、基礎となる実装はJavaにエラーを報告しませんでしたが、割り当ては成功しませんでした。この仮定を明示するには、オブジェクト変数の説明を提供する必要があります。しかし、 Androidの世界では、SystemServicesを介してsoundPoolを作成してみてください。このオブジェクトの正しい使用法についてリントが発生します。
  • 割り当て解除は、Application.classのonTerminate()メソッドで行うことができます// edit_4:super.onTerminate()のドキュメントでは、システムはアプリを強制終了します。したがって、アクティビティ内で割り当てを解除する必要があります。以下のコードスニペットを参照してください。

説明

JetPackコンポーネントのViewModelは主に、ビューの状態の保存と復元、およびモデルへのバインドを担当します。つまり、アクティビティ、フラグメント、場合によってはビュー全体のライフサイクルを処理します。これが、複数のフラグメント間でviewModelを共有する場合に備えて、アクティビティをライフサイクル所有者として使用する必要がある理由です。しかし、私はまだあなたのオブジェクトは単なるPOJOよりも複雑であり、私の上記の提案はあなたの期待される振る舞いをもたらすと思います。また、マルチスレッドの場合、ライフサイクルメソッドの正しい順序に依存してはならないことに注意してください。 Androidシステムによって特定の順序で呼び出されることが保証されているライフサイクルコールバックは限られていますが、頻繁に使用されるものはここに含まれていません。この場合、処理を開始する必要があります。より適切な時期に。

データは以前の状態に類似しているはずですが、正確な参照はhashCodeの実装によって異なりますが、これはJVM固有のものです。

//編集:

また、ParentFragmentは、他の人が参照する代わりに継承するクラスを作成したため、命名が不適切です。すべてのフラグメント内の特定の変数にアクセスする場合は、ナビゲーションコンポーネントがフラグメントマネージャーに直接アクセスできないようにするため、これをオブジェクト(シングルトン)として実装する必要があります。プレーンAndroidでは、1つのフラグメントが常にそのparentFragmentを参照できます。iffこのparentFragmentは、独自のchildFragmentManagerを使用してfragmentTransactionをコミットします。また、Activity-fragmentManagerによって追加されたフラグメントは、parentFragmentを持つことはありません。

// edit_2 + 3:

_ViewModelProvider(activity!!, ViewModelFactory())[clazz]
_

sharedViewModelを作成してアクセスするための正しい呼び出しです。ライフサイクルの所有者はアクティビティである必要があります。そうでない場合、fragmentTransactionが完了するたびにonCleared()メソッドへのコールバックが発生し、viewModelがすべての参照を解放してメモリリークを回避します。

// edit_4:オブジェクトが正しく初期化されなかったというのは、もう一度初期化しようとした場合にのみ発生する仮定でした。たとえば、適切でないvalでget()メソッドを使用するとします。それでも、この方法でオブジェクトを処理すると、そのライフサイクルがフラグメントの外にあることが保証されます。これが私の言い回しを明確にするためのコード例です。

// edit_5:オブジェクト参照が破損していないことをアサートするには、nullチェックを含めます(CustomClassの構築が重要でない場合のみ)

CustomApplicationを宣言

_class CustomApplication : Application() {

    companion object SharedInstances {

        /**
         *   Reference to an object accessed in various places in your application.
         *
         *   This property is initialized at a later point in time. In your case, once
         *   the user completed a required workflow in some fragment.
         *
         *   @Transient shall indicate that the state could also be not Serializable/Parcelable
         *              This _could_ require manually releasing the object.
         *              Also prohibits passing via safeArgs
         */
        @Transient var complex: CustomClass? = null
    }

}
_

クラス内の初期化と使用:

_class InitializeComplexStateFragment: Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        if (complex != null) return@onViewCreated // prohibit successive initialization.
        if (savedInstanceState != null) { /* The fragment was recreated but the object appears to be lost. */ }
        // do your heavy lifting and initialize your data at any point.
        CustomApplication.SharedInstances.complex = object : CustomClass() {
            val data = "forExampleAnSessionToken"
            /* other objects could need manual release / deallocation, like closing a fileDescriptor */
            val cObject = File("someFileDescriptorToBindTo")
        }
    }

}

class SomeOtherFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        CustomApplication.SharedInstances.complex?.let {
                // do processing
            }
            ?: propagateErrorStateInFragment()
    }

    private fun propagateErrorStateInFragment() { throw NotImplementedError("stub") }
}

_

必要に応じて割り当て解除

_class SomeActivity: Activity() {

    override fun onStop() {
        super.onStop()
        /* with multiple activities the effort increases */
        CustomApplication.complex?.close()
    }
}
_
1
Tomes