Android первый запуск приложения

Нашел в интернете кучу примеров с Navigation component из android jetpack, как переключаться между фрагментами и как при помощи sharedpreference отловить первый запуск. Но как мне только при первом запуске, один раз показать первый фрагмент, а при втором и последующих запусках второй фрагмент, я не нашел ни где. У меня получилось так, но все кто видел этот код сказали, что он ужасен, толком не дав пояснений в чем его ужасность и как, это исправить. Был бы благодарен, если бы получил пояснения что ИМЕННО в нем ужасно. Я только начинаю, по этому плохо понимаю архитектуру и как правильно писать подобное.

Код Activity

class MainActivity : AppCompatActivity() {
    var sharedPreferences: SharedPreferences? = null
    var sharedEditor: SharedPreferences.Editor? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (savedInstanceState == null) {
            sharedPreferences = getPreferences(MODE_PRIVATE)
            sharedEditor = sharedPreferences!!.edit()
            if (isFirstStart()) {
                supportFragmentManager.beginTransaction()
                    .setReorderingAllowed(true)
                    .add(R.id.fragment_container_view, FirstStartFragment::class.java, null)
                    .commit()
            } else {
                supportFragmentManager.beginTransaction()
                    .setReorderingAllowed(true)
                    .add(R.id.fragment_container_view, SecondStartFragment::class.java, null)
                    .commit()
            }
        }
    }

    private fun isFirstStart(): Boolean {
        return if (sharedPreferences!!.getBoolean("firstTime", true)) {
            sharedEditor!!.putBoolean("firstTime", false)
            sharedEditor!!.apply()
            true
        } else {
            false
        }
    }
}

Код фрагментов не стал приводить, так как в данном примере он не несет смысла. Мне нужно понять, как правильно реализовать "отлов" первого запуска приложения и в зависимости от этого показывать первый или второй фрагмент


Ответы (1 шт):

Автор решения: Vlad Orlinskas

На самом деле хранить состояние приложения в sharedPreferences не так и ужасно. Скорее речь об архитектуре. Например все действия с sharedPreferences необходимо выносить в отдельный класс и общаться с ним через ViewModel. Ваша активити должна заниматься только отображением контента, а логика должна быть во viewModel. В данном примере, что должно быть

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

        if (viewModel.isFirstAppStart()) {
            // запустить стартовый фрагмент
        } else {
           // запустить второй фрагмент
        }
}

Вот пример вынесеного sharedPreferences в отдельный пакет src/preferences

private const val FILE_NAME = "appState.pref"
private const val IS_FIRST_RUN = "isFirstRun"

@Singleton
class AppStatePrefs @Inject constructor(
    @ApplicationContext context: Context
) {

    private val sharedPreferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
    private var _isFirstRun: Boolean = sharedPreferences.getBoolean(IS_FIRST_RUN, true)

    var isFirstRun: Boolean
        @Synchronized
        get() = _isFirstRun
        @Synchronized
        set(value) {
            _isFirstRun = value
            sharedPreferences.edit()
                .putLong(IS_FIRST_RUN, value)
                .apply()
        }

}

Обратите внимание на то, как вынесены имя файла и ключ. В этом классе можно хранить например все, что касается состояния приложения, что пользователь уже видел и т.д. Вы можете заметить аннотации @Singleton @ApplicationContext. У вас этого не будет, это hilt, библиотека для внедрения зависимостей. Другими словами в приложении будет только один экземпляр этого класса, а значит мы можем из двух мест ассинхроно записывать поле isFirstRun, его гетер и сеттер нужно синхронизировать @Synchronized. Что бы пока один поток или корутина очищает значение, мы не посмотрели туда с другого места.

Ознакомтесь с внедрением зависимостей, можно сразу приступить к hilt

ViewModel, если кратко, вот так она может выглядеть в вашем случаи


class MainViewModel @ViewModelInject constructor(
        private val appStatePrefs: AppStatePrefs,
        // тут ваши репозитории и sharedPreferences
) : BaseViewModel() {
    // методы доступа
    
    fun isFirstAppStart(): Boolean {
        return appStatePrefs.isFirstRun
    }

}
→ Ссылка