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
}
}
しかし、残念ながら私には何の変化もありません。多分私のオブジェクトは大きすぎて正しく割り当てられませんか?
つまり、データをオブジェクト変数として参照していて、ViewModelを使用することを選択したときにいつでもアクセスできるようにしています。私には、あなたは自分の選択肢を考えすぎたように思えます。
提案
オブジェクトのライフサイクルは手動で管理されているようです。したがって、静的変数を使用する必要があります。これは、(コンパニオン)オブジェクト内のプロパティとしてKotlinに変換されます。マニフェスト内でカスタムアプリケーションクラスを宣言し、そのonCreate
メソッドでオブジェクトを割り当てて、このクラスのコンパニオンオブジェクトに配置することをお勧めします。もちろん、後でいつでも割り当てることができます。これにより、次のようになります。
YourApplication.mData
_を介していつでもアクセスできます。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の構築が重要でない場合のみ)
_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()
}
}
_