Kotlin - идиоматический способ создания фрагмента newInstance pattern

Лучшей практикой Android для создания Fragment является использование статического заводского метода и передача аргументов в Bundle через setArguments().

В Java это делается примерно так:

public class MyFragment extends Fragment {
    static MyFragment newInstance(int foo) {
        Bundle args = new Bundle();
        args.putInt("foo", foo);
        MyFragment fragment = new MyFragment();
        fragment.setArguments(args);
        return fragment;
    }
}

В Котлине это преобразуется в:

class MyFragment : Fragment() {
    companion object {
       fun newInstance(foo: Int): MyFragment {
            val args = Bundle()
            args.putInt("foo", foo)
            val fragment = MyFragment()
            fragment.arguments = args
            return fragment
        }
    }
}

Это имеет смысл поддерживать взаимодействие с Java, поэтому его все равно можно вызвать через MyFragment.newInstance(...), но есть ли более идиоматический способ сделать это в Kotlin, если нам не нужно беспокоиться о Java-взаимодействии?

Ответ 1

Мне нравится делать это так:

companion object {
    private const val MY_BOOLEAN = "my_boolean"
    private const val MY_INT = "my_int"

    fun newInstance(aBoolean: Boolean, anInt: Int) = MyFragment().apply {
        arguments = Bundle(2).apply {
            putBoolean(MY_BOOLEAN, aBoolean)
            putInt(MY_INT, anInt)
        }
    }
}

Изменение: с помощью расширений KotlinX, вы также можете сделать это

companion object {
    private const val MY_BOOLEAN = "my_boolean"
    private const val MY_INT = "my_int"

    fun newInstance(aBoolean: Boolean, anInt: Int) = MyFragment().apply {
        arguments = bundleOf(
            MY_BOOLEAN to aBoolean,
            MY_INT to anInt)
    }
}

Ответ 2

inline fun <reified T : Fragment>
    newFragmentInstance(vararg params: Pair<String, Any>) =
    T::class.java.newInstance().apply {
        arguments = bundleOf(*params)
    }'

Так оно и используется:

val fragment = newFragmentInstance<YourFragment>("key" to value)

Credit

bundleOf() можно взять у Анко

Ответ 3

Еще один способ сделать это я нашел здесь

class MyFragment: Fragment(){
  companion object{
    private val ARG_CAUGHT = "myFragment_caught"

    fun newInstance(caught: Pokemon):MyFragment{
      val args: Bundle = Bundle()
      args.putSerializable(ARG_CAUGHT, caught)
      val fragment = MyFragment()
      fragment.arguments = args
      return fragment
    }
    ...
  }
  ...
}

Ответ 4

Опоздал на вечеринку, но я считаю, что идиоматически это должно быть примерно так:

private const val FOO = "foo"
private const val BAR = "bar"

class MyFragment : Fragment() {
    companion object {
        fun newInstance(foo: Int, bar: String) = MyFragment().withArgs {
            putInt(FOO, foo)
            putString(BAR, bar)
        }
    }
}

с таким расширением:

inline fun <T : Fragment> T.withArgs(argsBuilder: Bundle.() -> Unit): T =
    this.apply {
        arguments = Bundle().apply(argsBuilder)
    }

или

companion object {
    fun newInstance(foo: Int, bar: String) = MyFragment().apply {
        arguments = bundleOf(
            FOO to foo,
            BAR to bar
        )
    }
 } 

Ключевым моментом является то, что частные константы не должны быть частью объекта-компаньона.

Ответ 5

Функция уровня пакета Kotlin

Как насчет того, что kotlin говорит использовать функцию уровня пакета вместо "статического" метода

MyFragment.kt

class MyFragment : Fragment() {

    .....

}

fun MyFragmentNewInstance(): MyFragment {
    return MyFragment()
}

MyActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    if (supportFragmentManager.findFragmentById(R.id.fragmentContainer) == null) {
        supportFragmentManager.beginTransaction()
            .add(R.id.fragmentContainer, MyFragmentNewInstance())
            .commit()
    }
}

Ответ 6

companion object {
  private const val NOTE_ID = "NOTE_ID"
  fun newInstance(noteId: Int?) = AddNoteFragment().apply {
  arguments =
      Bundle().apply { putInt(NOTE_ID, noteId ?: Int.MIN_VALUE) }
  }
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  super.onViewCreated(view, savedInstanceState)
  arguments?.let {
    noteId = it.getInt(NOTE_ID)
  } 
}