Разное

Lifecycle android fragment: Fragments  |  Android Developers

Содержание

Раскладываем на части FragmentLifecycleCallbacks / Блог компании ЮMoney / Хабр

Привет! Сегодня я продолжаю рассказывать про инструменты, которые почему-то обделили вниманием. В своей предыдущей статье я написал про возможности ActivityLifecycleCallbacks и как их можно применять не только для логирования жизненного цикла. Но кроме Activity есть еще и Fragment, и нам хотелось получить для них подобное поведение.

Не долго думая, я открыл поиск по классам в AndroidStudio (Cmd/Ctrl + O) и ввел туда FragmentLifecycleCallbacks. И каково же было мое удивление, когда поиск показал мне FragmentManager.FragmentLifecycleCallbacks. Самые нетерпеливые читатели писали про это в комментариях, поэтому вот продолжение всей этой истории. Скорее под кат!

Что это такое

Интерфейс наподобие ActivityLifecycleCallbacks, только для Fragment.

FragmentLifecycleCallbacks

/**
 * Callback interface for listening to fragment state changes that happen
 * within a given FragmentManager. 
 */
public abstract static class FragmentLifecycleCallbacks {
    /**
     * Called right before the fragment's {@link Fragment#onAttach(Context)} method is called.
     * This is a good time to inject any required dependencies or perform other configuration
     * for the fragment before any of the fragment's lifecycle methods are invoked.
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     * @param context Context that the Fragment is being attached to
     */
    public void onFragmentPreAttached(
        @NonNull FragmentManager fm, 
        @NonNull Fragment f,
        @NonNull Context context) {}

    /**
     * Called after the fragment has been attached to its host. Its host will have had
     * `onAttachFragment` called before this call happens.
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     * @param context Context that the Fragment was attached to
     */
    public void onFragmentAttached(
        @NonNull FragmentManager fm,
        @NonNull Fragment f,
        @NonNull Context context) {}

    /**
     * Called right before the fragment's {@link Fragment#onCreate(Bundle)} method is called. 
     * This is a good time to inject any required dependencies or perform other configuration
     * for the fragment.
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     * @param savedInstanceState Saved instance bundle from a previous instance
     */
    public void onFragmentPreCreated(
        @NonNull FragmentManager fm,
        @NonNull Fragment f,
        @Nullable Bundle savedInstanceState) {}

    /**
     * Called after the fragment has returned from the FragmentManager's call to
     * {@link Fragment#onCreate(Bundle)}. This will only happen once for any given
     * fragment instance, though the fragment may be attached and detached multiple times.
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     * @param savedInstanceState Saved instance bundle from a previous instance
     */
    public void onFragmentCreated(
        @NonNull FragmentManager fm,
        @NonNull Fragment f,
        @Nullable Bundle savedInstanceState) {}

    /**
     * Called after the fragment has returned from the FragmentManager's call to
     * {@link Fragment#onActivityCreated(Bundle)}.  This will only happen once for any given
     * fragment instance, though the fragment may be attached and detached multiple times.
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     * @param savedInstanceState Saved instance bundle from a previous instance
     */
    public void onFragmentActivityCreated(
        @NonNull FragmentManager fm,
        @NonNull Fragment f,
        @Nullable Bundle savedInstanceState) {}

    /**
     * Called after the fragment has returned a non-null view from the FragmentManager's
     * request to {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}.
     *
     * @param fm Host FragmentManager
     * @param f Fragment that created and owns the view
     * @param v View returned by the fragment
     * @param savedInstanceState Saved instance bundle from a previous instance
     */
    public void onFragmentViewCreated(
        @NonNull FragmentManager fm,
        @NonNull Fragment f,
        @NonNull View v,
        @Nullable Bundle savedInstanceState) {}

    /**
     * Called after the fragment has returned from the FragmentManager's call to
     * {@link Fragment#onStart()}. 
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     */
    public void onFragmentStarted(
        @NonNull FragmentManager fm, 
        @NonNull Fragment f) {}

    /**
     * Called after the fragment has returned from the FragmentManager's call to
     * {@link Fragment#onResume()}.
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     */
    public void onFragmentResumed(
        @NonNull FragmentManager fm, 
        @NonNull Fragment f) {}

    /**
     * Called after the fragment has returned from the FragmentManager's call to
     * {@link Fragment#onPause()}.
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     */
    public void onFragmentPaused(
        @NonNull FragmentManager fm, 
        @NonNull Fragment f) {}

    /**
     * Called after the fragment has returned from the FragmentManager's call to
     * {@link Fragment#onStop()}.
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     */
    public void onFragmentStopped(
        @NonNull FragmentManager fm,
        @NonNull Fragment f) {}

    /**
     * Called after the fragment has returned from the FragmentManager's call to
     * {@link Fragment#onSaveInstanceState(Bundle)}. 
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     * @param outState Saved state bundle for the fragment
     */
    public void onFragmentSaveInstanceState(
        @NonNull FragmentManager fm,
        @NonNull Fragment f,
        @NonNull Bundle outState) {}

    /**
     * Called after the fragment has returned from the FragmentManager's call to
     * {@link Fragment#onDestroyView()}.
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     */
    public void onFragmentViewDestroyed(
        @NonNull FragmentManager fm,
        @NonNull Fragment f) {}

    /**
     * Called after the fragment has returned from the FragmentManager's call to
     * {@link Fragment#onDestroy()}.
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     */
    public void onFragmentDestroyed(
        @NonNull FragmentManager fm,
        @NonNull Fragment f) {}

    /**
     * Called after the fragment has returned from the FragmentManager's call to
     * {@link Fragment#onDetach()}. 
     *
     * @param fm Host FragmentManager
     * @param f Fragment changing state
     */
    public void onFragmentDetached(
        @NonNull FragmentManager fm,
        @NonNull Fragment f) {}
}

В отличие от ActivityLifecycleCallbacks он управляется не самим Fragment, а FragmentManager, что дает ряд преимуществ. Например, у этого интерфейса методы с приставкой Pre-, которые вызываются до соответствующих методов Fragment. А методы без приставки вызываются после того, как сработают эти же методы Fragment.

К тому же FragmentLifecycleCallbacks — это абстрактный класс, а не интерфейс. Думаю, что это для того, чтобы у методов была реализация по умолчанию.

Но перейдем к интересному — как это запустить.

Как зарегистрировать

Чтобы заставить FragmentLifecycleCallbacks работать, его нужно зарегистрировать на FragmentManager. Для этого надо вызвать FragmentManager.registerFragmentLifecycleCallback(), передав в него два параметра: сам callback и флаг — recursive. Флаг показывает, нужно ли применить этот callback только к этому FragmentManager или его надо рекурсивно прокидывать во все childFragmentManager’ы, этого FragmentManager’а и дальше по иерархии.

FragmentLifecycleCallback стоит регистрировать до Activity.onCreate(), иначе мы можем получить не все события, например, при восстановлении состояния.

class FlcExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager
            .registerFragmentLifecycleCallbacks(
                ExampleFragmentLifecycleCallback(),
                true
            )

        super.onCreate(savedInstanceState)
    }
}

class ExampleFragmentLifecycleCallback : FragmentManager.FragmentLifecycleCallbacks()

Выглядит не очень красиво, и в некоторых ситуациях потребует заводить что-то вроде базовой Activity. Но если ты уже прочитал мою статью про ActivityLifecycleCallbacks, то знаешь, на что базовые Activity отлично заменяются =).

class ActivityFragmentLifecycleCallbacks :
    Application.ActivityLifecycleCallbacks,
    FragmentManager.FragmentLifecycleCallbacks() {

    override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
    ) {
        (activity as? FragmentActivity)
            ?.supportFragmentManager
            ?.registerFragmentLifecycleCallbacks(this, true)
    }
}

И тут мы получаем потрясающую синергию callback’ов. Благодаря этому решению мы теперь можем дотянуться почти до любого объекта Activity и Fragment, создаваемых в системе. И теперь, когда мы видим все как на ладони, можно заставить всю эту систему работать на нас.

Примеры использования

Сразу про dependency injection: да, теперь можно распространять зависимости по всему приложению, даже если у вас Single Activity Application. Помнишь пример из предыдущей статьи, про RequireCoolTool? То же самое можно сделать для всех Activity и Fragment в приложении. И ты уже догадался как, да? Но я все-равно покажу пример.

Dependency injection

interface CoolTool {
    val extraInfo: String
}

class CoolToolImpl : CoolTool {
    override val extraInfo = "i am dependency"
}

interface RequireCoolTool {
    var coolTool: CoolTool
}

class InjectingLifecycleCallbacks :
    Application.ActivityLifecycleCallbacks,
    FragmentManager.FragmentLifecycleCallbacks() {

    private val coolToolImpl = CoolToolImpl()

    override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
    ) {
        (activity as? RequireCoolTool)?.coolTool = coolToolImpl
        (activity as? FragmentActivity)
            ?.supportFragmentManager
            ?.registerFragmentLifecycleCallbacks(this, true)
    }

    override fun onFragmentPreCreated(
        fm: FragmentManager,
        f: Fragment,
        savedInstanceState: Bundle?
    ) {
        (f as? RequireCoolTool)?.coolTool = coolToolImpl
    }
}

class DIActivity : AppCompatActivity(), RequireCoolTool {

    override lateinit var coolTool: CoolTool

    override fun onCreate(savedInstanceState: Bundle?) {
        super. onCreate(savedInstanceState)

        setContentView(LinearLayout {
            orientation = LinearLayout.VERTICAL
            FrameLayout {
                layoutParams = LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f)
                Text(
                    """
                    DI example activity
                    CoolTool.extraInfo="${coolTool.extraInfo}"
                    """.trimIndent()
                )
            }
            FrameLayout {
                layoutParams = LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f)
                id = R.id.container
            }
        })

        supportFragmentManager.findFragmentById(R.id.container) ?: run {
            supportFragmentManager
                .beginTransaction()
                .add(R.id.container, DIFragment())
                .commit()
        }
    }
}

class DIFragment : Fragment(), RequireCoolTool {

    override lateinit var coolTool: CoolTool

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? =
        inflater. context.FrameLayout {
            setBackgroundColor(Color.LTGRAY)
            Text(
                """
                    DI example fragment
                    CoolTool.extraInfo="${coolTool.extraInfo}"
                    """.trimIndent()
            )
        }

}

И конечно же с Dagger’ом все тоже идеально работает.

Dagger

interface DaggerTool {
    val extraInfo: String
}

class DaggerToolImpl : DaggerTool {
    override val extraInfo = "i am dependency"
}

class DaggerInjectingLifecycleCallbacks(
    val dispatchingAndroidInjector: DispatchingAndroidInjector<Any>
) : Application.ActivityLifecycleCallbacks,
    FragmentManager.FragmentLifecycleCallbacks() {

    override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
    ) {
        dispatchingAndroidInjector.maybeInject(activity)
        (activity as? FragmentActivity)
            ?.supportFragmentManager
            ?.registerFragmentLifecycleCallbacks(this, true)
    }

    override fun onFragmentPreCreated(
        fm: FragmentManager,
        f: Fragment,
        savedInstanceState: Bundle?
    ) {
        dispatchingAndroidInjector. maybeInject(f)
    }
}

class DaggerActivity : AppCompatActivity() {

    @Inject
    lateinit var daggerTool: DaggerTool

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(LinearLayout {
            orientation = LinearLayout.VERTICAL
            FrameLayout {
                layoutParams = LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f)
                Text(
                    """
                    Dagger example activity
                    CoolTool.extraInfo="${daggerTool.extraInfo}"
                    """.trimIndent()
                )
            }
            FrameLayout {
                layoutParams = LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f)
                id = R.id.container
            }
        })

        supportFragmentManager.findFragmentById(R.id.container) ?: run {
            supportFragmentManager
                . beginTransaction()
                .add(R.id.container, DIFragment())
                .commit()
        }
    }
}

class DaggerFragment : Fragment() {

    @Inject
    lateinit var daggerTool: DaggerTool

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? =
        inflater.context.FrameLayout {
            Text(
                """
                Dagger example fragment
                DaggerTool.extraInfo="${daggerTool.extraInfo}"
                """.trimIndent()
            )
        }
}

@Module
class DaggerModule {
    @Provides
    fun provideDaggerTool(): DaggerTool {
        return DaggerToolImpl()
    }
}

@Module
abstract class DaggerAndroidModule {
    @ContributesAndroidInjector(modules = [DaggerModule::class])
    abstract fun contributeDaggerActivity(): DaggerActivity

    @ContributesAndroidInjector(modules = [DaggerModule::class])
    abstract fun contributeDaggerFragment(): DaggerFragment
}

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

Конечно, можно делать все то же самое, что и с Activity, например, отправлять аналитику.

Analytics

interface Screen {
    val screenName: String
}

interface ScreenWithParameters : Screen {
    val parameters: Map<String, String>
}

class AnalyticsCallback(
    val sendAnalytics: (String, Map<String, String>?) -> Unit
) : Application.ActivityLifecycleCallbacks, 
    FragmentManager.FragmentLifecycleCallbacks() {

    override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
    ) {
        if (savedInstanceState == null) {
            (activity as? Screen)?.screenName?.let {
                sendAnalytics(
                    it,
                    (activity as? ScreenWithParameters)?.parameters
                )
            }
        }
    }
}

class AnalyticsActivity : AppCompatActivity(), ScreenWithParameters {

    override val screenName: String = "First screen"

    override val parameters: Map<String, String> = mapOf("key" to "value")

    override fun onCreate(savedInstanceState: Bundle?) {
        super. onCreate(savedInstanceState)

        setContentView(LinearLayout {
            orientation = android.widget.LinearLayout.VERTICAL
            FrameLayout {
                layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f)
                Text(
                    """
                    Analytics example
                    see output in Logcat by "Analytics" tag
                    """.trimIndent()
                )
            }
            FrameLayout {
                layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f)
                id = R.id.container
            }
        })

        with(supportFragmentManager) {
            findFragmentById(R.id.container) ?: commit {
                add(R.id.container, AnalyticsFragment())
            }
        }
    }
}

class AnalyticsFragment : Fragment(), ScreenWithParameters {

    override val screenName: String = "Fragment screen"

    override val parameters: Map<String, String> = mapOf("key" to "value")

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? =
        inflater. context.FrameLayout {
            setBackgroundColor(Color.LTGRAY)
            Text(
                """
                Analytics example
                see output in Logcat by "Analytics" tag
                """.trimIndent()
            )
        }
}

А какие варианты использования знаешь ты?

5 распространенных ошибок при использовании архитектурных компонентов Android

Даже если вы не делаете этих ошибок, стоит о них помнить, чтобы не столкнуться с некоторыми проблемами в будущем.

1. Утечка наблюдателей LiveData во фрагментах

У фрагментов сложный жизненный цикл, и когда фрагмент отсоединяется и повторно присоединяется к Activity, то он не всегда уничтожается. Например, сохранённые фрагменты не уничтожаются во время изменений конфигурации. Когда это происходит, экземпляр фрагмента остаётся, а уничтожается только его View, поэтому onDestroy() не вызывается и состояние DESTROYED не достигается.

Это означает, что если мы начнём наблюдать LiveData в onCreateView() или позже (обычно в onActivityCreated()) и передадим фрагмент как LifecycleOwner:

class BooksFragment: Fragment() {

    private lateinit var viewModel: BooksViewModel

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

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(BooksViewModel::class.java)

        viewModel.liveData.observe(this, Observer { updateViews(it) })  // передача фрагмента как LifecycleOwner
    }
    
    ...
}

то мы будем передавать новый идентичный экземпляр Observer каждый раз, когда фрагмент повторно присоединяется, но LiveData не удалит предыдущих наблюдателей, потому что LifecycleOwner (фрагмент) не достиг состояния DESTROYED. Это в конечном итоге приводит к тому, что растёт число идентичных и одновременно активных наблюдателей, и один и тот же код из onChanged() выполняется несколько раз.

О проблеме изначально сообщалось здесь, а более подробное объяснение можно найти здесь.

Рекомендуемое решение состоит в том, чтобы использовать getViewLifecycleOwner() или getViewLifecycleOwnerLiveData() из жизненного цикла фрагмента, которые были добавлены в библиотеку поддержки 28.0.0 и AndroidX 1.0.0, так что LiveData будет удалять наблюдателей при каждом уничтожении View фрагмента:

class BooksFragment : Fragment() {

    ...

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(BooksViewModel::class.java)

        viewModel.liveData.observe(viewLifecycleOwner, Observer { updateViews(it) })
    }
    
    ...
}

2.

Перезагрузка данных после каждого поворота экрана

Мы привыкли помещать логику инициализации и настройки в onCreate() в Activity (и по аналогии в onCreateView() или позже во фрагментах), поэтому на этом этапе может быть заманчиво инициировать загрузку некоторых данных во ViewModels. Однако, в зависимости от вашей логики, это может привести к перезагрузке данных после каждого поворота экрана (даже если использовалась ViewModel), что в большинстве случаев просто бессмысленно.

Пример:

class ProductViewModel(
    private val repository: ProductRepository
) : ViewModel() {

    private val productDetails = MutableLiveData<Resource<ProductDetails>>()
    private val specialOffers = MutableLiveData<Resource<SpecialOffers>>()

    fun getProductsDetails(): LiveData<Resource<ProductDetails>> {
        repository.getProductDetails()  // Загрузка ProductDetails
        . ..                             // Получение ProductDetails и обновление productDetails LiveData
        return productDetails
    }

    fun loadSpecialOffers() {
        repository.getSpecialOffers()   // Загрузка SpecialOffers
        ...                             // Получение SpecialOffers и обновление specialOffers LiveData
    }
}

class ProductActivity : AppCompatActivity() {

    lateinit var productViewModelFactory: ProductViewModelFactory

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val viewModel = ViewModelProviders.of(this, productViewModelFactory).get(ProductViewModel::class.java)

        viewModel.getProductsDetails().observe(this, Observer { /*...*/ })  // (возможно) Повторный запрос productDetails после поворота экрана
        viewModel.loadSpecialOffers()                                       // (возможно) Повторный запрос specialOffers после поворота экрана
    }
}

Решение также зависит от вашей логики. Если репозиторий будет кешировать данные, то приведённый выше код, вероятно, будет в работать корректно. Также эту проблему можно решить другими способами:

  • Использовать что-то похожее на AbsentLiveData и начинать загрузку только, если данные не были получены;
  • Начинать загрузку данных, когда это действительно необходимо. Например, в OnClickListener;
  • И, вероятно, самое простое: поместить вызовы загрузки в конструктор ViewModel и использовать простые геттеры:
class ProductViewModel(
    private val repository: ProductRepository
) : ViewModel() {

    private val productDetails = MutableLiveData<Resource<ProductDetails>>()
    private val specialOffers = MutableLiveData<Resource<SpecialOffers>>()

    init {
        loadProductsDetails()           // ViewModel создаётся только один раз в ходе жизненных циклов Activity или фрагмента
    }

    private fun loadProductsDetails() {
        repository. getProductDetails()  
        ...                             
    }

    fun loadSpecialOffers() {           
        repository.getSpecialOffers()   
        ...                            
    }

    fun getProductDetails(): LiveData<Resource<ProductDetails>> { 
        return productDetails
    }

    fun getSpecialOffers(): LiveData<Resource<SpecialOffers>> {    
        return specialOffers
    }
}

class ProductActivity : AppCompatActivity() {

    lateinit var productViewModelFactory: ProductViewModelFactory

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val viewModel = ViewModelProviders.of(this, productViewModelFactory).get(ProductViewModel::class.java)

        viewModel.getProductDetails().observe(this, Observer { /*...*/ })    
        viewModel.getSpecialOffers(). observe(this, Observer { /*...*/ })   

        button_offers.setOnClickListener { viewModel.loadSpecialOffers() }
    }
}

3. Утечка ViewModels

Разработчики архитектуры чётко дали понять, что мы не должны передавать ссылки View во ViewModel.

но мы также должны быть осторожны при передаче ссылок на ViewModels другим классам. После завершения Activity (или фрагмента), ViewModel нельзя ссылаться ни на один объект, который может пережить Activity, чтобы ViewModel могла быть уничтожена сборщиком мусора.

В этом примере утечки во ViewModel передаётся слушатель Repository, который написан в стиле Singleton. Впоследствии слушатель не уничтожается:

@Singleton
class LocationRepository() {

    private var listener: ((Location) -> Unit)? = null

    fun setOnLocationChangedListener(listener: (Location) -> Unit) {
        this.listener = listener
    }

    private fun onLocationUpdated(location: Location) {
        listener?. invoke(location)
    }
}


class MapViewModel: AutoClearViewModel() {

    private val liveData = MutableLiveData<LocationRepository.Location>()
    private val repository = LocationRepository()

    init {
        repository.setOnLocationChangedListener {   
            liveData.value = it              
        }
    }
}

Решением здесь может быть удаление слушателя в методе onCleared(), сохранение его как WeakReference в репозитории или использование LiveData для связи между репозиторием и ViewModel:

@Singleton
class LocationRepository() {

    private var listener: ((Location) -> Unit)? = null

    fun setOnLocationChangedListener(listener: (Location) -> Unit) {
        this.listener = listener
    }

    fun removeOnLocationChangedListener() {
        this.listener = null
    }

    private fun onLocationUpdated(location: Location) {
        listener?.invoke(location)
    }
}


class MapViewModel: AutoClearViewModel() {

    private val liveData = MutableLiveData<LocationRepository.Location>()
    private val repository = LocationRepository()

    init {
        repository.setOnLocationChangedListener {   
            liveData.value = it                    
        }
    }
  
    override onCleared() {                           
        repository.removeOnLocationChangedListener() 
    }  
}

4. Позволять View изменять LiveData

Это не баг, но это противоречит разделению интересов. View — фрагменты и Activity — не должны иметь возможность обновлять LiveData и, следовательно, своё собственное состояние, потому что это ответственность ViewModels. View должны быть в состоянии только наблюдать LiveData.

Следовательно, мы должны инкапсулировать доступ к MutableLiveData:

class CatalogueViewModel : ViewModel() {

    // ПЛОХО: позволять изменять MutableLiveData
    val products = MutableLiveData<Products>()


    // ХОРОШО: инкапсулировать доступ к MutableLiveData
    private val promotions = MutableLiveData<Promotions>()

    fun getPromotions(): LiveData<Promotions> = promotions


    // ХОРОШО: инкапсулировать доступ к MutableLiveData
    private val _offers = MutableLiveData<Offers>()
    val offers: LiveData<Offers> = _offers


    fun loadData(){
        products.value = loadProducts()     // Другие классы смогут изменять products
        promotions.value = loadPromotions() // Только CatalogueViewModel может изменять promotions
        _offers.value = loadOffers()        // Only CatalogueViewModel может изменять _offers
    }
}

5. Создание зависимостей ViewModel после каждого изменения конфигурации

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

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

ViewModelProvider сохраняет экземпляр ViewModel, но не экземпляр ViewModelFactory, поэтому, если у нас есть такой код:

class MoviesViewModel(
    private val repository: MoviesRepository,
    private val stringProvider: StringProvider,
    private val authorisationService: AuthorisationService
) : ViewModel() {
    
    ...
}


class MoviesViewModelFactory( 
    private val repository: MoviesRepository,
    private val stringProvider: StringProvider,
    private val authorisationService: AuthorisationService
) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T { 
        return MoviesViewModel(repository, stringProvider, authorisationService) as T
    }
}


class MoviesActivity : AppCompatActivity() {

    @Inject
    lateinit var viewModelFactory: MoviesViewModelFactory

    private lateinit var viewModel: MoviesViewModel

    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_movies)

        injectDependencies()

        viewModel = ViewModelProviders.of(this, viewModelFactory).get(MoviesViewModel::class.java)
    }
    
    ...
}

то каждый раз, когда происходит изменение конфигурации, мы будем создавать новый экземпляр ViewModelFactory и, следовательно, без особой надобности создавать новые экземпляры всех его зависимостей (при условии, что они каким-то образом не ограничены).

class MoviesViewModel(
    private val repository: MoviesRepository,
    private val stringProvider: StringProvider,
    private val authorisationService: AuthorisationService
) : ViewModel() {
    
    ...
}


class MoviesViewModelFactory(
    private val repository: Provider<MoviesRepository>,             
    private val stringProvider: Provider<StringProvider>,         
    private val authorisationService: Provider<AuthorisationService>
) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {  
        return MoviesViewModel(repository.get(),                    
                               stringProvider.get(),                
                               authorisationService.get()
                              ) as T
    }
}


class MoviesActivity : AppCompatActivity() {

    @Inject
    lateinit var viewModelFactory: MoviesViewModelFactory

    private lateinit var viewModel: MoviesViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_movies)
      
        injectDependencies()

        viewModel = ViewModelProviders.of(this, viewModelFactory).get(MoviesViewModel::class.java)
    }
    
    ...
}

Перевод статьи «5 common mistakes when using Architecture Components»

Каковы различия между активностью и фрагментом Oh! Android

Согласно моим исследованиям, существует существенная разница в концепции стоп-качки и способах их существования:

Это две совершенно разные вещи:

Activity – это компонент приложения, который предоставляет экран, с помощью которого пользователи могут взаимодействовать, чтобы что-то сделать. Подробнее: http://developer.android.com/guide/components/activities.html

В то время как фрагмент представляет собой поведение или часть пользовательского интерфейса в Activity. http://developer.android.com/guide/components/fragments.html

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

  2. Активность может содержать 0 или несколько фрагментов на основе размера экрана. Фрагмент может быть повторно использован в нескольких действиях, поэтому он действует как компонент многократного использования в действиях.

  3. Фрагмент не может существовать независимо. Он должен быть всегда частью активности, тогда как активность может существовать без какого-либо фрагмента.

Согласно документации разработчика Android, разница между фрагментом и активностью в их жизненном цикле.

Ссылка Doc http://developer.android.com/guide/components/fragments.html#Lifecycle

Наиболее значительная разница в жизненном цикле между активностью и фрагментом заключается в том, как один хранится в соответствующем стеке. Активность по умолчанию помещается в задний стек действий, который управляется системой, когда он остановлен, чтобы пользователь мог вернуться к нему с помощью кнопки «Назад», как описано в разделе «Задачи и обратный стек»). Однако фрагмент помещается в задний стек, управляемый активностью хоста, только когда вы явно запрашиваете, чтобы экземпляр был сохранен, вызвав addToBackStack () во время транзакции, которая удаляет фрагмент.

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

& Для многоуровневых макетов вам нужно использовать fragment , которого вы не можете достичь с помощью activity .

Упражнение – это пользовательский интерфейс приложения, через который пользователь может взаимодействовать, а фрагмент – это часть Деятельности, он представляет собой суб-активность внутри деятельности, которая имеет свой собственный жизненный цикл, который проходит параллельно жизненному циклу активности.

 Activity LifeCycle Fragment LifeCycle onCreate() onAttach() | | onStart()______onRestart() onCreate() | | | onResume() | onCreateView() | | | onPause() | onActivityCreated() | | | onStop()__________| onStart() | | onDestroy() onResume() | onPause() | onStop() | onDestroyView() | onDestroy() | onDetach() 

Мероприятия
1. Деятельность – один из фундаментальных блоков приложений на платформе Android. Они служат отправной точкой для взаимодействия пользователя с приложением и также имеют центральное значение для того, как пользователь перемещается в приложении или между приложениями
2. Методы жизненного цикла размещаются ОС.
3. Жизненный цикл деятельности

Фрагменты
1. Фрагмент представляет собой поведение или часть пользовательского интерфейса в Activity. Вы можете объединить несколько фрагментов в одном действии для создания многоуровневого пользовательского интерфейса и повторного использования фрагмента в нескольких действиях. Вы можете представить фрагмент как модульный раздел действия, который имеет свой жизненный цикл, получает свои собственные события ввода и которые вы можете добавлять или удалять во время работы.
2. Методы жизненного цикла, размещаемые хостингом.
3. Жизненный цикл фрагмента

Основные различия между Activity и Fragment

  1. Активность – это компонент приложения, который предоставляет интерфейс пользователя, где пользователь может взаимодействовать. Фрагмент является частью деятельности, которая вносит свой собственный интерфейс в эту деятельность.
  2. Для планшета или если мобильный находится в ландшафте, то, используя фрагмент, мы можем показать два списка, например, список onle, чтобы показать, что имя состояния и другой список покажет описание состояния в одной активности, но с помощью Activity мы не сможем сделать то же самое.
  3. Активность не зависит от фрагмента. Но фрагмент зависит от Activity, он не может существовать независимо.
  4. Без использования фрагмента в Activity мы не можем создавать многоуровневые UI.but, используя несколько фрагментов в одной активности, мы можем создать многоуровневый интерфейс.
  5. Если мы создадим проект, используя только Activity, то его трудно справиться, но если мы будем использовать фрагменты, тогда структура проекта будет хорошей, и мы сможем легко справиться.
  6. Активность может содержать 0 или несколько фрагментов. Фрагмент может быть повторно использован в нескольких действиях, поэтому его действие подобно компоненту многократного использования в действиях.
  7. Деятельность имеет собственный жизненный цикл, но фрагмент имеет собственный жизненный цикл.
  8. Для Деятельности мы должны упомянуть в манифесте, но для фрагмента его не требуется.

Жизненный цикл фрагмента | Разработчики Android

Каждый экземпляр Fragment имеет
собственный жизненный цикл. Когда пользователь перемещается по вашему приложению и взаимодействует с ним, ваш
фрагменты переходят через различные состояния в своем жизненном цикле, так как они
добавлено, удалено, а также войти или выйти из экрана.

Для управления жизненным циклом Fragment реализует
LifecycleOwner , разоблачение
объект Lifecycle , который
вы можете получить доступ через
getLifecycle ()
метод.

Каждое возможное состояние Жизненного цикла представлено в
Lifecycle.State enum.

Создав Fragment поверх Lifecycle , вы можете использовать следующие методы:
и классы доступны для
Управление жизненными циклами с помощью компонентов с учетом жизненного цикла.
Например, вы можете отобразить местоположение устройства на экране.
с помощью компонента, учитывающего жизненный цикл. Этот компонент может автоматически
начинать прослушивание, когда фрагмент становится активным, и останавливаться, когда
фрагмент переходит в неактивное состояние.

В качестве альтернативы использованию
LifecycleObserver ,
Класс фрагмента включает методы обратного вызова, соответствующие каждому из
изменения в жизненном цикле фрагмента. Это включает
onCreate () ,
onStart () ,
onResume () ,
onPause () ,
onStop () и
onDestroy () .

Представление фрагмента имеет отдельный Жизненный цикл , который управляется независимо
от жизненного цикла фрагмента .Фрагменты сохраняют
Владелец жизненного цикла
для их просмотра, к которому можно получить доступ, используя
getViewLifecycleOwner ()
или же
getViewLifecycleOwnerLiveData () .
Наличие доступа к жизненному циклу представления полезно в ситуациях
где компонент с учетом жизненного цикла должен выполнять работу только в то время, когда
вид фрагмента существует, например, наблюдающий
LiveData , что имеется в виду только
для отображения на экране.

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

Фрагменты и менеджер фрагментов

Когда создается экземпляр фрагмента, он начинается с INITIALIZED
государственный. Чтобы фрагмент прошел оставшуюся часть своего жизненного цикла, он
должен быть добавлен в
Менеджер фрагментов . В
FragmentManager отвечает за определение состояния своего фрагмента
должен быть, а затем переместить их в это состояние.

Помимо жизненного цикла фрагмента, FragmentManager также отвечает за
прикрепляя фрагменты к их активности хозяина и отсоединяя их, когда
фрагмент больше не используется.Класс Fragment имеет два обратных вызова
методы, onAttach () и onDetach () , которые вы можете переопределить для выполнения
работают, когда происходит одно из этих событий.

Обратный вызов onAttach () вызывается, когда фрагмент добавляется в
FragmentManager и привязан к активности своего хоста. На данный момент
фрагмент активен, и FragmentManager управляет его жизненным циклом.
государственный. На данный момент методов FragmentManager , таких как
findFragmentById ()
верните этот фрагмент.

onAttach () всегда вызывается перед изменением состояния жизненного цикла.

Обратный вызов onDetach () вызывается, когда фрагмент был удален
от FragmentManager и отсоединяется от активности своего хоста. В
фрагмент больше не активен и не может быть получен с помощью
findFragmentById () .

onDetach () всегда вызывается после любого изменения состояния жизненного цикла.

Обратите внимание, что эти обратные вызовы не связаны с
FragmentTransaction
методы
прикрепить ()
а также
отсоединить () .Для получения дополнительной информации об этих методах см.
Фрагментарные транзакции.

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

Состояния жизненного цикла фрагмента и обратные вызовы

При определении состояния жизненного цикла фрагмента FragmentManager учитывает
следующие:

  • Максимальное состояние фрагмента определяется его FragmentManager
    фрагмент не может продвигаться дальше состояния своего FragmentManager .
  • Как часть FragmentTransaction , вы можете установить максимальное состояние жизненного цикла
    на фрагменте с использованием
    setMaxLifecycle () .
  • Состояние жизненного цикла фрагмента никогда не может быть выше, чем у его родительского. Для
    Например, родительский фрагмент или действие должны быть запущены до того, как его дочерний
    фрагменты. Точно так же дочерние фрагменты должны быть остановлены до того, как их родительский
    фрагмент или активность.

Внимание: Избегайте использования тега для добавления фрагмента с использованием XML, поскольку
тег позволяет фрагменту выйти за пределы состояния его
Менеджер фрагментов .Вместо этого всегда используйте
FragmentContainerView
для добавления фрагмента с помощью XML. Рисунок 1. Фрагмент Жизненный цикл состояний
и их связь как с обратными вызовами жизненного цикла фрагмента, так и с
вид фрагмента Жизненный цикл .

На рисунке 1 показано каждое из состояний жизненного цикла фрагмента и то, как они
относятся как к обратным вызовам жизненного цикла фрагмента, так и к
просмотреть Жизненный цикл .

По мере прохождения жизненного цикла фрагмента он движется вверх и
вниз через его состояния.Например, фрагмент, который добавлен
в верхнюю часть заднего стека перемещается вверх с СОЗДАНО на
НАЧАЛОСЬ ВОЗОБНОВЛЯЕТСЯ . И наоборот, когда фрагмент выскакивает из
задний стек, он перемещается вниз через эти состояния, начиная с
ВОЗОБНОВЛЕН ЗАПУЩЕН СОЗДАН и, наконец, УНИЧТОЖЕН .

Переходы между состояниями вверх

При движении вверх по состояниям жизненного цикла фрагмент сначала вызывает
связанный обратный вызов жизненного цикла для его нового состояния.После этого обратного вызова
закончено, соответствующие
Lifecycle.Event — это
передается наблюдателям с помощью фрагмента Lifecycle , за которым следует
представление фрагмента Жизненный цикл , если он был создан.

Фрагмент СОЗДАН

Когда ваш фрагмент достигает состояния CREATED , он добавляется в
FragmentManager и
на прикреплении ()
метод уже был вызван.

Это подходящее место для восстановления любого сохраненного состояния
связаны с самим фрагментом через
SavedStateRegistry .Обратите внимание, что вид фрагмента имеет , а не , которые были созданы в это время, и
любое состояние, связанное с представлением фрагмента, должно быть восстановлено только
после того, как представление было создано.

Этот переход вызывает
onCreate ()
Перезвоните. Обратный вызов также получает savedInstanceState
Bundle аргумент, содержащий любое состояние
ранее сохраненный
onSaveInstanceState () .
Обратите внимание, что savedInstanceState имеет значение null в первый раз
фрагмент создается, но он всегда не равен нулю для последующих
воссоздания, даже если вы не переопределите onSaveInstanceState () .Видеть
Сохранение состояния с фрагментами для подробностей
подробности.

Фрагмент СОЗДАН и просмотр ИНИЦИАЛИЗИРОВАН

Представление фрагмента Жизненный цикл создается только тогда, когда ваш Фрагмент
предоставляет действительный экземпляр View . В
в большинстве случаев вы можете использовать
конструкторы фрагментов
которые принимают @LayoutId , который автоматически увеличивает вид в
подходящее время. Вы также можете переопределить
onCreateView ()
для программного расширения или создания представления вашего фрагмента.

Если и только если ваше представление фрагмента создается с ненулевым значением
View , что View установлен на фрагменте и может быть получен с помощью
getView () . В
getViewLifecycleOwnerLiveData ()
затем обновляется новым
ИНИЦИАЛИЗАЦИЯ
Владелец жизненного цикла
соответствует виду фрагмента. В
onViewCreated ()
Обратный вызов жизненного цикла также вызывается в это время.

Это подходящее место для настройки начального состояния вашего представления,
начать наблюдение LiveData
экземпляры, обратные вызовы которых обновляют представление фрагмента, и для настройки
переходники на любые
RecyclerView или
ViewPager2 экземпляра
в представлении вашего фрагмента.

Фрагмент и представление СОЗДАНО

После того, как представление фрагмента было создано, предыдущее состояние представления, если оно есть,
восстанавливается, а жизненный цикл представления затем перемещается в
СОЗДАНО состояние. Владелец жизненного цикла представления также испускает
ON_CREATE событие
своим наблюдателям. Здесь вы должны восстановить любое дополнительное состояние, связанное с
с видом на фрагмент.

Этот переход также вызывает
onViewStateRestored ()
Перезвоните.

Фрагмент и вид НАЧАЛО

Настоятельно рекомендуется связать
Компоненты с учетом жизненного цикла для
состояние STARTED фрагмента, так как это состояние гарантирует, что
вид фрагмента доступен, если он был создан, и что он безопасен
для выполнения FragmentTransaction для дочернего FragmentManager
фрагмента. Если представление фрагмента не равно нулю,
view Lifecycle перемещается в STARTED сразу после фрагмента
Жизненный цикл перемещен в НАЧАЛО .

Когда фрагмент становится НАЧАЛО ,
onStart () обратный вызов
вызывается.

Примечание: Компоненты, такие как
ViewPager2 установить
максимум Жизненный цикл закадровых фрагментов до НАЧАЛО .

Fragment and View RESUMED

Когда фрагмент виден, все
Animator и
Переход эффектов имеют
закончено, и фрагмент готов для взаимодействия с пользователем. Фрагмент
Lifecycle переходит в состояние RESUMED , а
onResume ()
вызывается обратный вызов.

Переход к RESUMED является подходящим сигналом, чтобы указать, что
теперь пользователь может взаимодействовать с вашим фрагментом. Фрагменты, которые
не ВОЗОБНОВЛЕН не должен вручную устанавливать фокус на своих представлениях или пытаться
для обработки видимости метода ввода.

Переходы между состояниями вниз

Когда фрагмент перемещается вниз в состояние более низкого жизненного цикла,
соответствующий Lifecycle.Event
передается наблюдателям представлением фрагмента Lifecycle , если создается экземпляр,
за которым следует жизненного цикла фрагмента .После события жизненного цикла фрагмента
испускается, фрагмент вызывает связанный обратный вызов жизненного цикла.

Фрагмент и вид НАЧАЛО

Как пользователь начинает покидать фрагмент, а пока фрагмент все еще
видно, жизненный цикл с для фрагмента и для его просмотра перемещены назад
в состояние STARTED и выдать
ON_PAUSE событие
своим наблюдателям. Затем фрагмент вызывает свое
onPause () обратный вызов.

Фрагмент и представление СОЗДАНО

Когда фрагмент больше не виден, жизненный цикл с для фрагмента
и для его просмотра переводятся в состояние CREATED и выдают
ON_STOP событие
своим наблюдателям.Этот переход состояния запускается не только
родительская активность или фрагмент останавливается, но также путем сохранения
состояние родительским действием или фрагментом. Такое поведение гарантирует, что
событие ON_STOP вызывается перед сохранением состояния фрагмента. Этот
делает событие ON_STOP последней точкой, где безопасно выполнить
FragmentTransaction на дочернем FragmentManager .

Как показано на рисунке 2, порядок
onStop () обратный звонок
и сохранение состояния с помощью onSaveInstanceState () отличается в зависимости от API
уровень.Для всех уровней API до API 28 вызывается onSaveInstanceState () .
до onStop () .
Для уровней API 28 и выше порядок вызовов обратный.

Рисунок 2. Различия в порядке вызова для
onStop () и
onSaveInstanceState () .

Фрагмент СОЗДАН и просмотр УНИЧТОЖЕН

После всего выхода
анимации и переходы
завершена, а вид фрагмента отделен от окна,
вид фрагмента Lifecycle переводится в состояние DESTROYED и излучает
ON_DESTROY
событие для его наблюдателей.Затем фрагмент вызывает свое
onDestroyView ()
Перезвоните. На этом этапе вид фрагмента подошел к концу.
жизненный цикл и
getViewLifecycleOwnerLiveData ()
возвращает значение null .

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

Фрагмент УНИЧТОЖЕН

Если фрагмент удален или FragmentManager уничтожен,
жизненный цикл фрагмента переводится в состояние DESTROYED и отправляет
ON_DESTROY
событие для его наблюдателей.Затем фрагмент вызывает свое
onDestroy ()
Перезвоните. На этом этапе жизненный цикл фрагмента подошел к концу.

Дополнительные ресурсы

Для получения дополнительной информации о жизненном цикле фрагмента см.
следующие дополнительные ресурсы.

Направляющие

Блоги

фрагментов | Разработчики Android

A Фрагмент представляет собой
многоразовая часть пользовательского интерфейса вашего приложения. Фрагмент определяет и управляет своим собственным
layout, имеет собственный жизненный цикл и может обрабатывать собственные входные события.Фрагменты
не могут жить сами по себе — они должны быть размещены на каком-то мероприятии или другом
фрагмент. Иерархия представления фрагмента становится частью или присоединяется к ,
иерархия представления хоста.

Примечание. Некоторые библиотеки Android Jetpack, например
Навигация,
BottomNavigationView ,
и ViewPager2 , предназначены для
работа с фрагментами.

Модульность

Fragments обеспечивают модульность и возможность повторного использования в пользовательском интерфейсе вашей деятельности за счет
позволяя вам разделить пользовательский интерфейс на отдельные части.Мероприятия идеальны
место для размещения глобальных элементов вокруг пользовательского интерфейса вашего приложения, таких как
ящик навигации. И наоборот, фрагменты лучше подходят для определения и
управлять пользовательским интерфейсом отдельного экрана или части экрана.

Рассмотрим приложение, которое реагирует на различные размеры экрана. На больших экранах
приложение должно отображать панель статической навигации и список в виде сетки. На
экраны меньшего размера, приложение должно отображать нижнюю панель навигации и список в
линейная планировка. Управление всеми этими вариациями деятельности может быть
громоздкий.Отделение элементов навигации от содержимого может сделать это
процесс более управляемый. Затем действие отвечает за отображение
правильный интерфейс навигации, в то время как фрагмент отображает список с правильным
макет.

Рисунок 1. Две версии одного и того же экрана на разных
размеры экрана. Слева на большом экране находится панель навигации.
который контролируется действием и списком сетки, который контролируется
фрагмент. Справа небольшой экран содержит нижнюю панель навигации.
который контролируется действием и линейным списком, который контролируется
по фрагменту.

Разделение пользовательского интерфейса на фрагменты упрощает изменение
появление во время выполнения. Пока ваша активность находится в НАЧАЛО
состояние жизненного цикла или
выше, фрагменты можно добавлять, заменять или удалять. Вы можете вести учет
этих изменений в заднем стеке, который управляется действием, что позволяет
изменения должны быть отменены.

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

Следующие шаги

Дополнительную документацию и ресурсы, относящиеся к фрагментам, см. Ниже.

Начало работы

Другие темы

Видео

фрагментов Android и их жизненный цикл

фрагменты Android и их жизненный цикл

Добро пожаловать, вы успешно реализовали приложения, используя действия, описанные в предыдущих главах.Здесь мы начнем с самой важной темы — фрагментов.

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

Fragment предоставляет нам 2 основные вещи:

  1. Модульность
  2. Адаптивность
Модульность

Например, мы использовали приложение Gmail в наших мобильных телефонах, а также в планшетах.Чем отличается пользовательский интерфейс? В зависимости от размера экрана он выглядит как на изображении ниже.

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

Адаптивность

Мы также можем взять один пример WhatsApp, чтобы лучше понять его. Мы видим три вкладки в WhatsApp, смахивание по которым откроет нам еще одну вкладку. Чат / Статус / Звонки — это три фрагмента, которые изменяются в этой конкретной области.

Есть несколько замечаний по поводу фрагментов:

  • Фрагмент имеет свой макет и свое поведение с обратными вызовами жизненного цикла.
  • Вы можете добавлять или удалять фрагменты в действии. Активность выполняется, которая находится в состоянии возобновленного жизненного цикла.
  • Вы можете объединить несколько фрагментов в одном действии для создания многопанельного пользовательского интерфейса.
  • Фрагмент можно повторно использовать в нескольких действиях.
  • Жизненный цикл фрагмента тесно связан с жизненным циклом его активности хоста, что означает, что когда действие находится в состоянии паузы, все фрагменты, доступные в действии, также останавливаются.
  • Фрагменты, добавленные в Android API в Android 3.0, API версии 11 для поддержки гибкого пользовательского интерфейса на больших экранах.

У фрагмента есть свой жизненный цикл, который проходит в рамках жизненного цикла действия.

Когда на экране появляется фрагмент: —

  1. onAttach () — этот метод вызывается первым, чтобы знать, что наш фрагмент был прикреплен к действию. Мы передаем Activity, в которой будет размещен наш фрагмент.
  2. onCreate () — этот метод вызывается при инициализации экземпляра фрагмента сразу после onAttach, когда фрагмент присоединяется к активности узла.
  3. onCreateView () — метод, вызываемый, когда пора фрагменту впервые отрисовать свой пользовательский интерфейс. Чтобы нарисовать пользовательский интерфейс для вашего фрагмента, вы должны вернуть компонент View из этого метода, который является корнем макета вашего фрагмента. Вы можете вернуть null, если фрагмент не предоставляет пользовательский интерфейс.
  4. onActivityCreated () — этот метод вызывается, когда Activity завершает свой метод onCreate ().
  5. onStart () — этот метод вызывается, когда фрагмент виден.
  6. onResume () — этот метод вызывается, когда фрагмент виден и позволяет пользователю взаимодействовать с ним.Фрагмент возобновляется только после возобновления активности.

Когда фрагмент выходит за пределы экрана: —

  1. onPause () — этот метод вызывается, когда фрагмент не позволяет пользователю взаимодействовать; фрагмент будет заменен другим фрагментом, или он будет удален из активности или активности фрагмента, что называется паузой.
  2. onStop () — этот метод вызывается, когда фрагмент больше не виден; фрагмент будет изменен другим фрагментом, или он будет удален из активности или активности фрагмента, называемой остановкой.
  3. onDestroyView () — этот метод вызывается, когда представление и связанные ресурсы, созданные в onCreateView (), удаляются из иерархии представлений активности и уничтожаются.
  4. onDestroy () — этот метод вызывается, когда фрагмент выполняет окончательную очистку.
  5. onDetach () — этот метод вызывается, когда фрагмент отключается от его активности узла.

Это все о том, как фрагмент появляется в операции и исчезает. Прохладный!! Мы узнали о Фрагментах.

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

Жизненный цикл фрагмента в Android — GeeksforGeeks

xml версия = "1.0" кодировка = "utf-8" ?>

< 0004 9055 9055 LinearLayout 9055 макет = «match_parent»

android: layout_height = «match_parent»

android: фон = android:

android: ориентация = "вертикальный"

инструменты: контекст = ".MainActivity » >

< TextView

android: id = android: layout_width = "match_parent"

android: layout_height = "wrap_content"

android: layout_margin4000 9554

android: layout_marginBottom = "20dp"

android: fontFamily = "@ font / roboto"

android: text заголовок "

android: textAlignmen t = «центр»

android: textColor = "@android: color / holo_green_light"

android: textSize 0004

android: textStyle = «жирный» />

< 0005

0005

"@ + id / button1"

android: layout_width = "fill_parent"

android: layout_height = "wrap_content4 _content» layout_marginStart = "20dp"

android: layout_marginEnd = "20dp"

android: background = "# 4CAF50"

font4 / android: font4Fam4to 9

android: onClick = "selectFragment"

android: text = "@ string / fragment1_button"

54 000

54 000 text "@android: color / background_light"

android: textSize = "18sp"

android: textStyle =

< Кнопка

android: id = "@ + id / button2"

android: layout_width = "fill_parent"

_height "wrap_content"

android: layout_marginStart = "20dp"

android: layout_marginTop = android4nd android: nd "20dp"

android: layout_marginBottom = "20dp"

android: фон = "# 4CAF4000" "# 4CAF4000 9000 4 = "@ font / roboto"

9055 4 android: onClick = "selectFragment"

android: text = "@ string / fragment2_button"

android: android:

android: android: color / background_light "

android: textSize = " 18sp "

android: textStyle = 000" bold " /

< фрагмент

android: id = "@ + id / fragment_section"

example.fragments_backup.FragmentOne "

android: layout_width = " match_parent "

android: layout_height 00040004000 00040004 = "10dp"

android: layout_marginEnd = "10dp"

android: layout_marginBottom = "@ layout / fragment_one" />

LinearLayout >

Android Fragment Lifecycle2000 Объяснено, готовите ли вы 9000 Java Code Gee? для собеседования или хотите внедрить фрагменты в свой ап p, вы должны знать о жизненном цикле фрагмента.

Большинство из нас хорошо знает жизненный цикл активности Android, но не фрагментарно жизненный цикл как таковой.

Как жизненный цикл фрагмента взаимодействует с жизненным циклом действия? В чем разница между добавлением фрагмента и его заменой?

Мы рассмотрим все эти вопросы ниже.

Жизненный цикл фрагмента Android

Жизненный цикл фрагмента в Android в основном состоит из этих 8 методов:

  1. onCreate : вызывается для инициализации неграфических компонентов.
  2. onCreateView : вызывается для раздувания XML-макета фрагмента.
  3. onStart
  4. onResume
  5. onPause
  6. onStop
  7. onDestroyView
  8. onDestroy life Чтобы изучить последовательность их звонков, я создал образец проекта.

    Создание примера приложения

    Мы проанализируем жизненный цикл фрагмента двумя способами.Сначала добавляя фрагмент динамически, а затем статически.

    Создайте новый проект в Android Studio. Это будет базовый проект, в который мы просто добавим базовые операторы журнала во все методы жизненного цикла.

    В этом проекте добавьте фрагмент с именем LifecycleFragment и переопределите методы жизненного цикла. Также создайте простой макет xml, если он еще не создан.

    01

    02

    03

    04

    05

    06

    07

    08

    09

    10

    11

    000

    000 120002

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    2

    29

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    0005 49

    50

    51

    52

    53

    class LifecycleFragment: Fragment () {

    9 0554 var LOG_TAG = "Androidville"

    сопутствующий объект {

    @JvmStatic

    000) 902 (000) (Lifecycle) }

    переопределение функции onCreate (savedInstanceState: Bundle?) {

    super .onCreate (savedInstanceState)

    Log.d (LOG_TAG, "LifecycleFragment: onCreate (), вызываемый" )

    000

    000 поверх 0005

    надувной модуль: LayoutInflater, контейнер: ViewGroup ?,

    savedInstanceState: Bundle?

    ): View? {

    Лог.d (LOG_TAG, "LifecycleFragment: onCreateView () с именем" )

    return inflater.inflate (R.layout.fragment_lifecycle, контейнер, false ) false }

    функция переопределения onStart () {

    Log.d (LOG_TAG, "LifecycleFragment: onStart () под названием" )

    superonStart ()

    }

    функция переопределения onResume () {

    Log.d (LOG_TAG, " " на LifecycleesFragment)

    super .onResume ()

    }

    Override Fun onPause () {

    d (LOG_TAG, "LifecycleFragment: onPause () называется" )

    super .onPause ()

    000

    {

    Log.d (LOG_TAG, "LifecycleFragment: onStop (), называется" )

    super .onStop ()

    переопределить удовольствие от уничтожения () {

    Журнал.d (LOG_TAG, "LifecycleFragment: onDestroy () под названием" )

    super .onDestroy ()

    000 над

    54

    {

    Log.d (LOG_TAG, "LifecycleFragment: onDestroyView () называется" )

    super .onDestroyView ()

    }

    }

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

    Вот файл макета:

    01

    02

    03

    04

    05

    06

    07

    08

    09

    100002 9000

    14

    "1.0 " кодировка = " utf-8 " ?>

    android: layout_width = " match_parent "

    android: layout_parent4 000 000: layout_parent4 90 инструменты: context = ".LifecycleFragment" >

    android: layout_width = ight554000 ightcontent50002 = "wrap_content"

    android: layout_centerInParent = "true"

    android: text = "Hello from Fragment!"

    />

    />

    / RelativeLayout>

    Динамическое добавление фрагмента

    Вот код для MainActivity.kt для динамического добавления фрагмента:

    01

    02

    03

    04

    05

    06

    07

    08

    09

    10 13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    000

    000

    0002 26

    000

    000

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    0002 43002 43

    47

    класс MainActivity: AppCompatActivity () {

    var TAG: String = "ANDROIDVIL LE "

    var LOG_TAG = " Androidville "

    переопределение функции onCreate (savedInstanceState: Bundle?) {

    super.onCreate (SavedInstanceState)

    setContentView (R.layout.activity_main)

    Log.d (LOG_TAG, "MainActivity: onCreate () настройка ()

    }

    частный fun setupFragment () {

    supportFragmentManager.beginTransaction ()

    .add (R.id.contentFrame, LifecycleFragment.newInstance (), TAG)

    .commit ()

    9555

    9554 переопределить удовольствие onRestart () {

    Log.d (LOG_TAG, "MainActivity: onRestart () называется" )

    super .onRestart ()

    }

    переопределение функции onStart () {

    Log.d (LOG_TAG, "9000" MainActivity):

    super .onStart ()

    }

    функция переопределения onResume () {

    d (LOG_TAG, "MainActivity: onResume () called" )

    super .onResume ()

    000

    {

    Log.d (LOG_TAG, "MainActivity: onPause () называется" )

    super .onPause ()

    }

    функция отмены блокировки при остановке () {

    Журнал.d (LOG_TAG, "MainActivity: onStop () называется" )

    super .onStop ()

    }

    }

    estroy {

    Log.d (LOG_TAG, "MainActivity: onDestroy () называется" )

    super .onDestroy ()

    }

    Вот макет activity_main.xml :

    01

    02

    03

    04

    05

    06

    07

    08

    09

    10

    11

    "1.0" encoding = "utf-8" ?>

    android: layout_width = 000 "match_parent" android: layout_height = "match_parent"

    инструменты: context = ".MainActivity " >

    android: id = " @ + id / contentFrame match "

    android: 9_parent layout =

    android:

    android: layout_height = "wrap_content" />

    Статически добавим фрагмент 2 к статическому фрагменту 2. просто добавьте элемент

    в Activity_main .xml и задайте его имя классу kotlin.
    Расположение:

    01

    02

    03

    04

    05

    06

    07

    08

    09

    10

    0002

    0002

    11

    "1.0" encoding = "utf-8" ?>

    android: layout_width = 000 "match_parent" android: layout_height = "match_parent"

    инструменты: context = ".MainActivity " >

    <фрагмент

    android: id = " @ + id / lifecycle_fragment "

    9000 name4 .androidfragmentlifecycleexample.LifecycleFragment "

    android: layout_width = " match_parent "

    android: layout_height = " match_parentay "

    MainActivity.kt файл:

    01

    02

    03

    04

    05

    06

    07

    08

    09

    10

    11

    11

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    000

    000

    000

    000

    000 31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    класс

    00 MainActivity:

    : String = «ANDROIDVILLE»

    var LOG_TAG = «Androidville»

    переопределить удовольствие onCreate (savedInstanceState: Bundle?) {

    super .onCreate (savedInstanceState)

    setContentView (R.layout.activity_main)

    Log.d (LOG_TAG, "MainActivity: onCreate () вызов"

    переопределить удовольствие onRestart () {

    Log.d (LOG_TAG, "MainActivity: onRestart () под названием" )

    onRestart ()

    }

    переопределение функции onStart () {

    Log.d (LOG_TAG, "9000" MainActivity):

    super .onStart ()

    }

    функция переопределения onResume () {

    d (LOG_TAG, "MainActivity: onResume () called" )

    super .onResume ()

    000

    {

    Log.d (LOG_TAG, "MainActivity: onPause () называется" )

    super .onPause ()

    }

    функция отмены блокировки при остановке () {

    Журнал.d (LOG_TAG, "MainActivity: onStop () называется" )

    super .onStop ()

    }

    }

    estroy {

    Log.d (LOG_TAG, "MainActivity: onDestroy () называется" )

    super .onDestroy ()

    }

    Последовательность обратного вызова жизненного цикла фрагмента Android

    Наконец, давайте посмотрим, какова последовательность обратного вызова жизненного цикла во фрагментах при запуске приложения.

    Добавляется динамически

    Когда мы добавляем фрагмент динамически, происходит следующая последовательность обратного вызова.

    Здесь видно, что действие с нетерпением создается. Действия onCreate, onStart вызываются первыми.

    Затем вызываются фрагменты onCreate, onCreateView и onStart. Наконец, вызывается onResume Activity и Fragment соответственно.

    При выходе из приложения:

    Здесь сначала уничтожается активность, а затем фрагмент. Итак, onPause, onStop и onDestroy активности вызываются сначала перед фрагментом соответственно.

    Статически добавлено

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

    При выходе из приложения:

    То же, что и статическое добавление. Сначала уничтожается активность, а затем фрагмент.

    Добавить или заменить фрагмент

    Как следует из названия, когда вы добавляете фрагмент, он добавляется поверх того, который уже находится в контейнере. Но если вы вызовете replace, фрагмент в контейнере будет заменен новым.

    Это означает, что если вы попытаетесь получить фрагменты по тегу в своей деятельности, вы не найдете его, если заменили фрагмент, а не добавляли поверх него.

    Жизненный цикл фрагмента · Разработка для Android

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

    onAttach (активность)

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

    onCreate (пакет)

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

    onCreateView (LayoutInflator, ViewGroup, Bundle)

    Этот метод вызывается, когда пора создать представление для фрагмента. Единственная цель этого метода — создать все макеты и представления, вероятно, путем расширения файла макета, которые используются во фрагменте, и вернуть их, чтобы их можно было добавить в структуру пользовательского интерфейса действия. Этот метод вызывается после onCreate () при первом запуске, но может выполняться в другом порядке в зависимости от того, как отображается фрагмент.Если фрагмент возобновляет работу из приостановленного состояния, этот метод будет вызван для воссоздания представления фрагмента из приостановленного состояния. Подробнее об этом в событиях жизненного цикла ниже.

    onActivityCreated (пакет)

    Этот метод вызывается, когда содержащее действие завершает выполнение собственного метода onCreate (), если фрагмент был добавлен к действию во время onCreate (). Если фрагмент добавлен в действие после того, как onCreate () уже завершился, этот метод все равно будет вызываться сразу после вызова onCreateView ().Этот метод можно использовать для изменения представлений и прикрепления данных к вашим представлениям, используя данные, которые были настроены в onCreate (), и настройки представлений в onCreateView (). Важно, чтобы вы не выполняли эту настройку во время onCreateView (), поскольку любые операции, выполняемые в этом методе, могут привести к тому, что создание вашего представления в других классах займет больше времени, чем необходимо.

    onStart ()

    Здесь методы жизненного цикла начинают немного ближе связываться с жизненным циклом деятельности. Метод onStart () вызывается, когда фрагмент впервые становится видимым для пользователя, после вызова onStart () в содержащем действии.Если onStart () уже был выполнен в действии, этот метод по-прежнему вызывается после onActivityCreated () для поддержания согласованности событий жизненного цикла.

    onResume ()

    Этот метод выполняет почти то же самое, что и метод действия onResume (). На данный момент фрагмент находится на переднем плане и принимает взаимодействия с пользователем. Как и onStart (), этот метод вызывается после того, как действие вызывает onResume (), и, если onResume () уже был вызван в действии, этот метод всегда будет следовать за onStart () для поддержания согласованности событий жизненного цикла.

    onPause ()

    Как и в случае с действием, метод onPause () вызывается, когда фрагмент все еще виден в приложении, но больше не принимает взаимодействие с пользователем. Это может быть связано с тем, что содержащее действие было приостановлено или потому, что операция фрагмента изменяет действие и содержит фрагменты. Метод onPause () всегда должен быть первым методом, вызываемым, когда фрагмент также скрывается или уничтожается.

    onStop ()

    Опять же, как и в случае с действием, метод onStop () вызывается, когда фрагмент больше не виден пользователю.Это связано либо с тем, что содержащее действие было остановлено, либо фрагмент скрывается или удаляется с помощью транзакции фрагмента. Это также может произойти, если фрагмент помещен в backstack.

    onDestroyView ()

    Этот метод вызывается после onStop () и используется для очистки любых устаревших ресурсов, связанных с макетом фрагмента. Когда фрагменты больше не видны пользователю, система сохраняет их состояние, чтобы их можно было быстро восстановить, но представления уничтожаются для экономии памяти.Когда этот метод вызывается, вы больше не можете получить доступ к макету фрагмента и связанным представлениям. Если фрагмент возобновляется, onCreateView () вызывается снова, после чего следуют все другие события жизненного цикла, необходимые для возврата фрагмента на передний план. Если вместо этого фрагмент полностью удаляется из действия или само действие завершается, то фрагмент продолжит выполнение указанных ниже событий жизненного цикла и будет уничтожен.

    onDestroy ()

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

    onDetach ()

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

    Список литературы

    http://developer.android.com/guide/components/fragments.html

    жизненных циклов фрагментов в эпоху реактивных ранцев

    У фрагментов… Сложный жизненный цикл, мягко говоря.Давайте посмотрим на них и на то, как все они вписываются в современный мир Jetpack.

    Жизненный цикл экземпляра

    Начнем с середины, с жизненного цикла самого экземпляра класса Fragment . Я творчески назову это жизненным циклом экземпляра .

    Технически этот жизненный цикл начинается, когда создается экземпляр Fragment , обычно с помощью вызова конструктора или обычного вызова метода newInstance . Он также может быть создан системой Android автоматически в процессе восстановления состояния приложения.Жизненный цикл экземпляра заканчивается сборщиком мусора после того, как на него больше не ссылаются.

    С практической точки зрения, в этом жизненном цикле есть две пары методов жизненного цикла, которые вы будете использовать в своем коде. Методы onAttach и onDetach сообщают вам, когда фрагмент подключен к контексту Context через FragmentManager , а методы onCreate и onDestroy — это то место, где вы обычно должны инициализировать свой фрагмент и освободить его. любые используемые им ресурсы соответственно.

    Поскольку это жизненный цикл объекта Fragment в памяти, любые данные, которые вы храните в свойствах Fragment, останутся там только в рамках этого жизненного цикла. Существует несколько событий, которые могут привести к воссозданию вашего экземпляра Fragment и потере таких значений, поэтому механизм savedInstanceState предлагает сохранение и восстановление состояния с помощью методов onSaveInstanceState и onCreate .

    Наиболее известен случай, когда экземпляр фрагмента удаляется и создается новый, когда приложение претерпевает изменение конфигурации * — так же, как и в случае с действиями.

    Мы часто упрощаем изменения конфигурации с на изменения ориентации , то есть поворот экрана, поскольку это то, с чем разработчики обычно сталкиваются в первую очередь (и чаще всего) при изучении разработки под Android. Однако есть и другие события, которые также вызывают такое же изменение конфигурации и последующее воссоздание экземпляра, например, изменение размера экрана или языка, или переключение темного режима.

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

    * Запрещает использование устаревшего метода setRetainInstance

    Жизненный цикл представления

    Увеличивая масштаб жизненного цикла фрагмента, описанного выше, мы обнаруживаем более короткий жизненный цикл, вложенный в жизненный цикл экземпляра. Это жизненный цикл представления , жизненный цикл экземпляра View , на котором фрагмент отображает пользовательский интерфейс.

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

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

    Жизненный цикл представления начинается с вызовов onCreateView и onViewCreated (для наполнения и инициализации соответственно) и заканчивается onDestroyView .

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

    Логический жизненный цикл

    Наконец, отойдя от рассмотренных нами жизненных циклов, мы обнаружим самый широкий жизненный цикл фрагмента. Я назову это логическим жизненным циклом за отсутствием лучшего термина.Этот жизненный цикл длится все логическое время жизни экрана. Он начинается, когда Фрагмент впервые создается, включает все изменения конфигурации, через которые он проходит, и заканчивается, когда Фрагмент окончательно уничтожается, без его дальнейшего воссоздания.

    Это жизненный цикл, в котором живет класс Jetpack ViewModel , и он предназначен для решения проблемы очистки данных восстановления фрагмента и отмены операций, запущенных во фрагменте. При воссоздании фрагмента связанный с ним экземпляр ViewModel остается таким же, как и для предыдущего экземпляра фрагмента, что означает, что данные, помещенные в модель ViewModel , сохранят эти изменения конфигурации, как и операции, запущенные в Область видимости ViewModel .

    С точки зрения методов в коде, логический жизненный цикл начинается, когда создается первый экземпляр Fragment и соответствующий экземпляр ViewModel (инициализация может быть помещена в конструктор ViewModel), и заканчивается методом onCleared объекта ViewModel, который вызывается незадолго до вызовов onDestroy и onDetach самого последнего экземпляра Fragment.

    Обратите внимание, что хотя этот жизненный цикл сохранит изменения конфигурации, модели ViewModel по-прежнему хранятся в памяти и не сохраняются на диске, поэтому они (и этот жизненный цикл) по-прежнему не будут длиться до смерти процесса.(Возможно, следует также назвать четвертый жизненный цикл, который включает перезапуски процессов в дополнение к воссозданиям внутри процесса 🤔).

    Если вы хотите сохранить данные на уровне ViewModel вашего приложения, вы можете использовать механизм SavedStateHandle . Я писал об этом в статье «Ранний взгляд на ViewModel SavedState».
    и его продолжение, «Глубокое погружение в расширяемую экономию состояния».
    .

    Теперь, когда мы рассмотрели жизненные циклы, принадлежащие фрагменту, давайте посмотрим, как мы встречаемся с ними на практике при использовании некоторых из последних конструкций Jetpack.

    Владельцы жизненного цикла и наблюдение за LiveData

    Jetpack представил концепцию LifecycleOwner — вещь с жизненным циклом. Их можно использовать по-разному, одним из наиболее заметных является наблюдение за LiveData .

    Первоначально класс поддержки Fragment сам по себе был LifecycleOwner , которому принадлежал жизненный цикл экземпляра фрагмента (от onCreate до onDestroy ). Это то, что вы могли использовать для настройки наблюдений, что означало передачу этого в качестве параметра методу наблюдения :

      переопределить удовольствие onCreate (savedInstanceState: Bundle?) {
        супер.onCreate (сохраненныйInstanceState)
    
        viewModel.state.observe (это, Наблюдатель {...})
    }
      

    Класс LiveData обладает способностью самостоятельно очищать зарегистрированных наблюдателей, когда связанный жизненный цикл заканчивается, что означает, что регистрация наблюдателя в onCreate с самим фрагментом в качестве владельца жизненного цикла приведет к его автоматическому удалению в onDestroy . Аккуратный!

    Однако возникла проблема, когда значения из LiveData , подобные этому, использовались для обновления пользовательского интерфейса.Наблюдатели LiveData получают начальное состояние, когда они начинают наблюдение, а затем они получают уведомление об изменениях, когда они происходят. Наличие наблюдателя, настроенного в жизненном цикле экземпляра , означало, что при воссоздании представления фрагмента новый расширенный макет не будет заполнен до тех пор, пока значение LiveData не изменится, поскольку в противном случае не было бы причин для снова вызовите подключенного наблюдателя. Теоретически он уже видел последнюю ценность.

    Решение? Перемещение наблюдателя к методу onViewCreated , чтобы новый был настроен для каждого представления созданного фрагмента.Первоначальное уведомление наблюдателя должно гарантировать, что новый пользовательский интерфейс заполнен данными.

      переопределить удовольствие onViewCreated (view: View, savedInstanceState: Bundle?) {
        super.onViewCreated (просмотр, savedInstanceState)
    
        viewModel.state.observe (это, Наблюдатель {...})
    }
      

    Однако, если вы сделали это, все еще используя фрагмент как LifecycleOwner , вы внесли ошибку в код. Вы добавляете новых наблюдателей в начале жизненного цикла представления, которые могут выполняться несколько раз, но они будут удалены только в конце жизненного цикла экземпляра.Они будут зарегистрированы с на ViewCreated до наDestroy , и по мере того, как фрагмент получит новые просмотры, они начнут накапливаться.

    Например, когда ваш фрагмент находится в своем втором жизненном цикле представления после того, как представление было уничтожено, а затем снова раздуто, каждое значение, помещенное в LiveData , будет запускать два наблюдателя, дважды выполняя один и тот же код обновления пользовательского интерфейса.

    Как ни странно, эта проблема продолжалась некоторое время после первоначального выпуска компонентов архитектуры.Вы можете найти обсуждения и обходные пути еще в 2017 году в этом выпуске GitHub.

    Достаточно популярной была реализация ViewLifecycleFragment Кристофа Бейлса, которая расширила фрагмент и добавила в него новый жизненный цикл, который начинается в onViewCreated и заканчивается onDestroyView .

    Если вы хотите правильно наблюдать за LiveData , вы бы расширили этот класс вместо Fragment и использовали viewLifecycleOwner для настройки наблюдателя:

      class ProfileFragment: ViewLifecycleFragment () {
        переопределить удовольствие onViewCreated (view: View, savedInstanceState: Bundle?) {
            супер.onViewCreated (просмотр, savedInstanceState)
        
            viewModel.state.observe (viewLifecycleOwner, Наблюдатель {...})
        }
    }
      

    Команда компонентов архитектуры в конечном итоге решила исправить этот недостаток класса Fragment , представив собственное свойство viewLifecycleOwner с таким же поведением в классе AndroidX Fragment , что устраняет необходимость в обходном пути.

      class ProfileFragment: Fragment () {
        переопределить удовольствие onViewCreated (view: View, savedInstanceState: Bundle?) {
            супер.onViewCreated (просмотр, savedInstanceState)
        
            viewModel.state.observe (viewLifecycleOwner, Наблюдатель {...})
        }
    }
      

    TL; DR : при наблюдении за LiveData для обновления состояния пользовательского интерфейса сделайте это в onViewCreated и используйте viewLifecycleOwner для настройки наблюдателя.

    Интеграции сопрограмм

    А теперь поговорим о сопрограммах. Если вы используете модель ViewModel , то большая часть вашей фоновой работы, например получение данных из сети или с диска, должна выполняться в логическом жизненном цикле .Это гарантирует, что эти выборки данных продолжаются через изменения конфигурации, так как вполне вероятно, что вы не захотите отменять свои запросы и запускать новые, когда они произойдут (что вам придется сделать, если вы выполнили эти вызовы во фрагменте, жизненный цикл экземпляра ().

    Это означает, что вы должны использовать сопрограммы, которые привязаны к вашей модели ViewModel . Легкий способ сделать это - использовать расширение Jetpack viewModelScope :

      class ProfileViewModel: ViewModel () {
        fun loadProfile () {
            viewModelScope.запуск {
                // Приостановка вызовов для получения данных
            }
        }
    }
      

    Эта область автоматически отменяется при вызове метода onCleared . Если вместо этого вы вводите или создаете свою собственную область видимости в модели ViewModel , не забудьте отменить ее в методе onCleared вручную.


    Еще одна интересная интеграция сопрограмм в Jetpack - это lifecycleScope . Это свойство расширения на LifecycleOwner , и оно дает вам область действия, которая отменяется по окончании жизненного цикла данного владельца.

    Чтобы запустить сопрограммы в жизненном цикле экземпляра фрагмента, вы можете использовать lifecycleScope (что соответствует this.lifecycleScope ). Корутины, запущенные в этой области, будут очищены в на Destroy :

    .

      class ProfileFragment: Fragment () {
        переопределить веселье onCreate (savedInstanceState: Bundle?) {
            super.onCreate (savedInstanceState)
        
            lifecycleScope.launch {
                // Приостановка материала
            }
        }
    }
      

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

    Для этого вам нужно будет использовать viewLifecycleOwner.lifecycleScope при запуске сопрограмм. Поскольку они будут очищены в onDestroyView , вы должны настроить их в соответствующем методе в начале жизненного цикла, который равен onViewCreated :

    .

      переопределить удовольствие onViewCreated (view: View, savedInstanceState: Bundle?) {
        super.onViewCreated (просмотр, savedInstanceState)
    
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.stateFlow.collect {...}
        }
    }
      

    Что дальше?

    Очевидно, что жизненные циклы фрагментов сложны, и вы должны помнить о различиях между ними при работе с этими API.

    На прошлогоднем саммите разработчиков Android было ускользнуло, что жизненные циклы экземпляра и представления могут быть объединены, так что остается только жизненный цикл представления, а когда представление фрагмента разрушается, сам экземпляр уничтожается вместе с ним.Это еще не сделано, и, очевидно, это будет серьезное изменение в API-интерфейсах фрагментов. Пришло время для Fragment2 ?

    Если вы хотите узнать больше о правильном использовании Jetpack, взгляните на мой обзор кода проекта Pokedex, который затрагивает различные API Jetpack.

    И наконец, крик. Я уже писал об этих темах раньше, но идея для этой статьи возникла вначале из разговоров с членами команды RayWenderlich.com (Эвана Маргейн и Нишант Шривастава), а затем этот твит Васи Дробушкова заставил меня сесть и написать его. все до.

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

    RainbowCake - это платформа архитектуры Android, предоставляющая инструменты и руководство для создания современных приложений Android.

    В архитектурах представлений, подобных MVVM, состояния представления недостаточно для взаимодействия модели представления с представлением. Они должны быть дополнены какими-то событиями, которые сопряжены с рядом проблем, о которых вам следует знать.

    Правильная обработка состояния пользовательского интерфейса - одна из важнейших задач приложений Android. Вот мои субъективные мысли о разных подходах, которые я предпочитаю выбирать и почему.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *