From Fragments to Activity: the Lambda Way

Activities have their lifecycle, and the same goes forFragments. Hence, communication from Fragments to Activities is notoriously problematic.

The communication between Fragments and Activities cannot be as simple as passing a listener to the fragment that the activity could implement: the Activity would soon rotate and the Fragment would keep a reference to an Activity that had died and would leak. Google recommends a “good old pattern” to achieve this goal and limit the risk of leaks. In this article, we will demonstrate that lambdas are a better solution, more expressive, and easier to maintain.

We have created a Github repo that contains the code of our solution: https://github.com/stephanenicolas/activtity-fragment-lambda

The Good Old Way

The official way to make a Fragment communicate with its hosting Activity involves creating a communication interface and to implement it in the Activity:

MainActivity, the Good Old Way

The Fragment will get a reference to its hosting Activity and use an ugly and risky cast to communicate to the Activity via the communication interface:

HeadLinesFragment, the Good Old Way

There are various issues with this approach:

  1. the casting statement is ignominious and fails at runtime.
  2. you don’t see clearly how the Fragment and the Activity are linked. The link is more implicit than explicit.

This makes the Good Old Way crash-prone, and hard to maintain. Let’s try something different!

The Lambda Way

The Lambda Way will favor composition over inheritance. We don’t want our Activity class to implement the communication interface anymore:

MainActivity, The Lambda Way

Our communication interface is modified to use any Activity. Thus it becomes independent of the Activity instance and won’t leak it:

HeadlineListener, The Lambda Way

We will see below why it uses a generic and it is Serializable.

The Fragment will pass its current hosting Activity instance to the listener when it wants to communicate with it:

HeadLinesFragment, The Lambda Way

And here’s he key: the activity will now set a listener to the Fragment, using a simple method reference.

Setting the listener, The Lambda Way

What is this method reference : MainActivity::onArticleSelected ?

This is one of the tough questions of this approach, and it gives us an opportunity for a good dive into the Java syntax.

Actually, MainActivity::onArticleSelected is an ambiguous java statement. It can represent:

  • either a reference to a static method;
  • OR a reference to non static method of object of a specific type.

If onArticleSelected was a static method of MainActivity, we could refer to it via MainActivity::onArticleSelected . Referencing a static method is the most typical use of this type of reference.

But, in our case, onArticleSelected is **not** static. So, we would usually refer to it as mainActivityInstance::onArticleSelected . This method reference would point to the non-static method onArticleSelected . You could use, for instance, in a Rx chain to map an observable of positions:`

Using the normal method reference this::onArticleSelected in a Rx Chain.

So, what does MainActivity::onArticleSelected refer to ??

Internally, all non-static methods in Java are compiled in the same way as static methods, but with one hidden first parameter of the type of its enclosing class. Thus, the 2 methods below are almost equal from the byte code perspective:

Non-Static methods have a hidden first parameter of the type of the enclosing class.

The method reference MainActivity::onArticleSelected is a reference to the second form of this method. It is now pointing to a method that takes 2 parameters: a MainActivity and a int.

This second method is interesting for us because it is independent of the MainActivity instance. Hence, a reference to it doesn’t leak the activity instance!

From a lambda perspective, the meaning of this method reference is now slightly different: it is indeed a method that accepts a MainActivity and invokes the method onArticleSelected on this activity:

MainActivity::onArticleSelected is equivalent to
activity -> ((MainActivity)activity).onArticleSelected(position)

In other words, MainActivity::onArticleSelected is a method reference to a HeadLineListener implementation that is independent of the activity and will work with any instance of MainActivity .

For a fine-grained explanation of method references, please visit the official Java documentation.

Disambiguating MainActivity::onArticleSelected

As explained above, this method reference can be misunderstood by the Java compiler. It could understand it as a static method reference. In order to disambiguate it, both the interface HeadlineListener, and the method HeaderFragment#setHeadlineListener will use generics to trigger what is called a target type inference:

HeadlineListener, The Lambda Way. The generic trigger a target type inference.
Setter method. The generic trigger a target type inference.

Thanks to these 2 generics, the compiler understands that the future method reference passed to setHeadlineListener as a reference to a non static method of object of a specific type, and it will only accept references that point to a method inside a class that extends Activity . This last constraint is reasonable because, from a Fragment perspective, we can easily get the hosting Activity using getActivity(). This is why we can now invoke our listener method on the current Activity :

Surviving the Fragment Lifecycle

The last point is to make sure that our fragment still knows about its callback when it dies and it is recreated by the FragmentManager (e.g. after a rotation). For this, we will serialize the listener into the arguments of the fragments.

In our Github repo, we opted for a very structured approach to create our HeadlineFragment by using a builder pattern. The builder will let you create the fragment with an appropriate listener. Of course, the builder is not mandatory for the “lambda way” to work, you just need to serialize the lambda and you can do it the way you want.

It is generally advised not to serialize lambdas in Java, exactly for the same reasons as inner classes. Nevertheless, this issue is softened here as:

  • on Android, serialization is short term and cannot create issues during application upgrades and class changes.
  • the lambda/method reference is fully static and stateless, and is intrinsically protected from any serialization issue as it can’t contain any reference to a non serializable element nor refer to entities that could have died (e.g. the Activity or the Fragment).

Conclusion

This new approach is quite technical, we agree on this. It also infringes the general rule of not serializing lambdas. However, we believe that this approach brings a few benefits to our code base:

  • The link between Activities and Fragments is now explicit. It can be traced easily inside our IDE as opposed to implementing an interface the Good Old Way.
  • We got rid of the hideous casting of the Good Old Way.
  • We favor composition over inheritance.
  • Code looks quite close to the rest of our modern Rx chains.
  • When using a Fragment with such an API, using the listener interface is as easy as using a method reference and most of the complexity of the solution goes behind the scene.
  • It enforces building fragments using a builder pattern to setup the arguments which reduces bugs at runtime.

Thanks to Samuel Guirado Navarro for bullet proofing the approach.

Let us know what you think, and thx for reading this article !
With 💚 , the Groupon Android team.