2

I'm looking to find a solution on how to inject fragment and pass arguments to it. And i didn't find any proper solution because injecting the fragment means by the constructor which is not safe for states.

Is there any way to do this, without calling the newInstance pattern ?

Thanks,

Best.

JohnDot
  • 119
  • 1
  • 1
  • 2

1 Answers1

0

Because Android manages the lifecycle of your Fragment, you should separate the problems of passing state into the Fragment through its bundle and injecting the Fragment with injectable deps. Usually, the best way to separate these is by providing a static factory method, which you might be calling the newInstance pattern.

public class YourFragment extends Fragment {

  // Fragments must have public no-arg constructors that Android can call.
  // Ideally, do not override the default Fragment constructor, but if you do
  // you should definitely not take constructor parameters.

  @Inject FieldOne fieldOne;
  @Inject FieldTwo fieldTwo;

  public static YourFragment newInstance(String arg1, int arg2) {
    YourFragment yourFragment = new YourFragment();
    Bundle bundle = new Bundle();
    bundle.putString("arg1", arg1);
    bundle.putInt("arg2", arg2);
    yourFragment.setArguments(bundle);
    return yourFragment;
  }

  @Override public void onAttach(Context context) {
    // Inject here, now that the Fragment has an Activity.
    // This happens automatically if you subclass DaggerFragment.
    AndroidSupportInjection.inject(this);
  }

  @Override public void onCreate(Bundle bundle) {
    // Now you can unpack the arguments/state from the Bundle and use them.
    String arg1 = bundle.getString("arg1");
    String arg2 = bundle.getInt("arg2");
    // ...
  }
}

Note that this is a different type of injection than you may be used to: Rather than getting a Fragment instance by injecting it, you are telling the Fragment to inject itself later once it has been attached to an Activity. This example uses dagger.android for that injection, which uses subcomponents and members-injection methods to inject @Inject-annotated fields and methods even when Android creates the Fragment instance outside of Dagger's control.

Also note that Bundle is a general key-value store; I've used "arg1" and "arg2" instead of coming up with more creative names, but you can use any String keys you'd like. See Bundle and its superclass BaseBundle to see all of the data types Bundle supports in its get and put methods. This Bundle is also useful for saving Fragment data; if your app is interrupted by a phone call and Android destroys your Activity to save memory, you can use onSaveInstanceState to put form field data into the Bundle and then restore that information in onCreate.

Finally, note that you don't need to create a static factory method like newInstance; you could also have your consumers create a new YourFragment() instance and pass in a particular Bundle design themselves. However, at that point the Bundle structure becomes a part of your API, which you may not want. By creating a static factory method (or Factory object or other structure), you allow the Bundle design to be an implementation detail of your Fragment, and provide a documented and well-kept structure for consumers to create new instances.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • Hi Jeff, So it means i have to create separate different entities for each arguments ? How can i achieve to add several primary types like String ? Thanks. – JohnDot Aug 30 '18 at 20:22
  • @JohnDot Your static factory method (e.g. `newInstance`) can take as many parameters as it wants, and Bundle is a key-value store (like a Map) where you can insert any kind of data you'd like. I've only listed one argument here ("arg") but you can put in as many as you'd like. See [the Bundle docs](https://developer.android.com/reference/android/os/Bundle) for all of the getters and setters it supports. – Jeff Bowman Aug 30 '18 at 20:25
  • Sorry i missread your first comment, i meant the 2 inject fields. But nevermind. So if i understand well, you are saying we can't inject a fragment with arguments ? We have to go with the static factory method newInstance ? – JohnDot Aug 30 '18 at 20:30
  • @JohnDot I'm saying _regardless of arguments you should never use constructor injection on Fragment_ (or any other object Android can create and destroy on its own, which includes Application, Activity, View, and Service). You can still inject a Fragment, but the right place to do that is in `onAttach` as above. The right way to pass state and parameters ("arguments") into a Fragment is using a Bundle; you might choose to encapsulate that with a static factory method. (You could also just require your consumers to call `new` and then call `setArguments` with a particular Bundle structure.) – Jeff Bowman Aug 30 '18 at 20:36
  • @JohnDot I've updated the answer to include some of the information in the comments, and to clarify how the Bundle logic and DI code works. – Jeff Bowman Aug 30 '18 at 20:45
  • So, to explain a bit more what i'm doing : I do this in my module : `@FragmentScope @ContributesAndroidInjector abstract fun testFragment(): TestFragment` And this in my activity : `@Inject lateinit var testFragment: TestFragment` And my TestFragment : `class TestFragment @Inject constructor() : DaggerFragment()` What i'm doing is a bad practice then ? I don't know how to inject my fragment inside my activity without the constructor injection... – JohnDot Aug 30 '18 at 21:10
  • @JohnDot There are three types of objects: Objects that don't need DI (e.g. Strings and Lists and data objects and other _newables_), objects that need DI that you get from a factory (like a Component or Provider), and objects where you call `new` and _they inject themselves_. Fragments in dagger.android are the third category: you don't need to worry about getting them from the graph in your Activity; you can call `new` or `newInstance` instead, and then in `onAttach` that call to `AndroidSupportInject.inject(this)` will _let the fragment inject itself_. – Jeff Bowman Aug 30 '18 at 21:27