Нека започнем с историята, която ме насочи да напиша тази статия. Беше ми дадена задача да подготвя библиотека за други разработчици, която да бъде възможно най-лесна за използване, като същевременно предоставя широко персонализиране. Идеята беше да има фрагмент, който разработчикът да постави в своето приложение и със стартирането на жизнения цикъл всичко да работи — просто начало, но големи възможности за промени.

Какви бяха мислите ми?

1. Мога да създам фрагмент, да поставя необходимите обекти в него и след това да изложа обширни интерфейси, чрез които разработчиците биха манипулирали състояния. Заключението? Беше достатъчно сложен и някои обекти бяха извън моя контрол - те не принадлежаха директно към моя SDK, което означаваше, че ще трябва да напиша много код, който няма да допринесе много.

2. Нека самият разработчик инжектира тези обекти чрез конструктора във фрагмента. Заключението? Това не е добра практика, защото когато устройството се завърти, фрагментът се унищожава и винаги се избира празен конструктор. Ако Фрагментът няма такъв, това е КРАШ!

Започнах да търся начин да съчетая тези факти и тогава попаднах на FragmentFactory. Неговата задача е да създава фрагменти. В конфигурацията по подразбиране не прави много, което би могло да ни бъде полезно, но когато създадем наша собствена реализация, се оказва, че можем да „нарушим правилата“ и да създадем фрагменти с непразен конструктор!

Revolshen… спри да говориш, покажи кода!

Както желаеш!

  1. Добавете KTX зависимост от фрагмент и създайте фрагмент според вашите предпочитания

В build.gradle

implementation 'androidx.fragment:fragment-ktx:1.5.5'

След това създайте фрагмент, както желаете:

class FragmentWithNonEmptyConstructor(
private val firstBigObject: FirstBigObject,
private val secondBigObject: SecondBigObject): Fragment() {
...
}

2. Създайте CustomFragmentFactory и добавете своя собствена логика, ако създаденият фрагмент е вашият фрагмент.

class CustomFragmentFactory(
    private val firstBigObject: FirstBigObject,
    private val secondBigObject: SecondBigObject)
    : FragmentFactory() {

    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        return when(className){
            FragmentWithNonEmptyConstructor::class.java.name -> FragmentWithNonEmptyConstructor(firstBigObject, secondBigObject)
            else -> super.instantiate(classLoader, className)
        }
    }
}

3. Заменете FragmentFactory по подразбиране с вашата CustomFragmentFactory

class MainActivity() : AppCompatActivity() {

  private val firstBigObject = FirstBigObject()
  private val secondBigObject = SecondBigObject()

  override fun onCreate(savedInstanceState: Bundle?) {
      supportFragmentManager.fragmentFactory = CustomFragmentFactory(firstBigObject, secondBigOjbect)        
      super.onCreate(savedInstanceState)
      ...
  }
...
}

Искаме FragmentManager да използва нашата сменена версия на FragmentFactory. Важно е да замените FragmentFactory възможно най-рано преди да извикате super.onCreate(…).

4. Празнувайте и можете да изпиете чаша кафе!

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