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
:
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:
There are various issues with this approach:
- the casting statement is ignominious and fails at runtime.
- you don’t see clearly how the
Fragment
and theActivity
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:
Our communication interface is modified to use any Activity
. Thus it becomes independent of the Activity
instance and won’t leak it:
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:
And here’s he key: the activity will now set a listener to the Fragment
, using a simple method reference.
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:`
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:
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 toactivity -> ((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:
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 theFragment
).
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.