astroboy
astroboy

Reputation: 1078

android navigation pass arguments to fragment constructor

I have created Navigation Drawer Activity:

class MainActivity : AppCompatActivity() {

    private lateinit var appBarConfiguration: AppBarConfiguration

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val toolbar: Toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)

        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        val navView: NavigationView = findViewById(R.id.nav_view)
        val navController = findNavController(R.id.nav_host_fragment)

        appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)
    }
}

And I have mobile_navigation.xml:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation"
    app:startDestination="@id/databaseFragment">
    <fragment
        android:id="@+id/databaseFragment"
        android:name="com.acmpo6ou.myaccounts.ui.DatabaseFragment"
        android:label="fragment_database_list"
        tools:layout="@layout/fragment_database_list" >
        <action
            android:id="@+id/actionCreateDatabase"
            app:destination="@id/createDatabaseFragment" />
    </fragment>
    <fragment
        android:id="@+id/createDatabaseFragment"
        android:name="com.acmpo6ou.myaccounts.ui.CreateDatabaseFragment"
        android:label="create_edit_database_fragment"
        tools:layout="@layout/create_edit_database_fragment" />
</navigation>

The start destination is DatabaseFragment. However there is a problem, here is my DatabaseFragment:

class DatabaseFragment(
        override val adapter: DatabasesAdapterInter,
        val presenter: DatabasesPresenterInter
) : Fragment(), DatabaseFragmentInter {
...
    companion object {
        @JvmStatic
        fun newInstance(
                adapter: DatabasesAdapterInter,
                presenter: DatabasesPresenterInter
        ) = DatabaseFragment(adapter, presenter)
    }
}

As you can see my DatabaseFragment should receive two arguments to its constructor: adapter and presenter. This is because of dependency injection, in my tests I can instantiate DatabaseFragment passing through mocked adapter and presenter. Like this:

...
val adapter = mock<DatabasesAdapterInter>()
val presenter = mock<DatabasesPresenterInter>()
val fragment = DatabaseFragment(adapter, presenter)
...

It works with tests, but it doesn't work with android navigation. It seems that Android Navigation Components create DatabaseFragment instead of me, but they don't pass any arguments to fragment's constructor and it fails with error that is too long to post it here.

Is there a way to tell Navigation Components so that they pass appropriate arguments to my fragments when instantiating them?

Thanks!

Upvotes: 1

Views: 4199

Answers (3)

astroboy
astroboy

Reputation: 1078

I fixed everything pretty easily using default arguments, like this:

class DatabaseFragment(
        override val adapter: DatabasesAdapterInter = DatabasesAdapter(),
        val presenter: DatabasesPresenterInter = DatabasesPresenter()
) : Fragment(), DatabaseFragmentInter {
...
    companion object {
        @JvmStatic
        fun newInstance() = DatabaseFragment()
    }
}

Upvotes: 0

Lheonair
Lheonair

Reputation: 498

I just want to add to i30mb1 answer:

Is there really a necessity for you to pass those two arguments in the constructor?

  • As far as I know and as far as I have experimented with MVP, each view should have a presenter. So for example when I create a new fragment, I create a new presenter for it. Then the parent activity should have another presenter. If you need that presenter so that the fragment can make changes in the Activities view, you could implement interfaces, but that's another topic.

  • If you ever need to pass simple arguments using navigation like POJOS or even simplier objects like Strings etc.. you can use SafeArgs https://developer.android.com/guide/navigation/navigation-pass-data

Upvotes: 1

i30mb1
i30mb1

Reputation: 4776

Short answer is no, you can not pass arguments to Fragment.

All subclasses of Fragment must include a public no-argument constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the no-argument constructor is not available, a runtime exception will occur in some cases during state restore.

Upvotes: 2

Related Questions