神刀安全网

From Prefab House to Lego House

Before joining my current employer I have worked for an app agency and one thing I have noticed is that we were building every app entirely new from scratch even if we were building a similar app for the same customer with similar UI. We hadn’t reusable components. In this blog post I want to share some thoughts and lessons learned while building such reusable components.

At my current employer, Tickaroo , we were facing the same situation three years ago. Although Tickaroo is not an app agency, it’s a startup for sport enthusiasts, we are building apps for our partners from the world of sports. One of this partner is the Olympia Verlag , a renowned publisher in Germany for which we have started building (and are still maintaining) their native android and iOS apps for one of their magazines called kicker (football magazine, which displays football news, live updated and so on) three years ago.

After having released the first versions successfully of the kicker app in 2013 the Olympia Verlag was pleased with our work and charged us with building another app called MeinVerein , also a football app where you can select your favorite football club and then the app displays news and games about your football club.

While kicker app is quite a huge app that can be used for free (with advertisement), MeinVerein is a paid only app (subscription based). MeinVerein’s app functionality can be seen as some kind of subset from kicker app.

One thing was clear from the very beginning: since the same designer who already has designed the kicker app was going to design MeinVerein app too the UI should be quite similar for both apps. In fact, this was desired by our customer Olympia Verlag since they wanted to have a consistent design branding across all of their apps. Let’s compare the UI of the apps:

From Prefab House to Lego House

Basically MeinVerein app displays the same data in more or less the same way as kicker app does with just some other colors / theme and the main navigation of both apps are different (navigation drawer vs. ViewPager). Some screens, for example the live updates screens, are completely identical.

Let’s build a prefabricated house

Before we started with the development of MeinVerein app we, the iOS team and the android team, get together and discussed how we could build that app without much effort and in little time. The leading iOS developer, which by the way is the most experienced developer in our company, suggested to put all the stuff from the kicker app into a library. Software development is much about experience. Hence is priceless to have experienced developers in your team and we from the android team had zero experience on how to build such a shared library thing. His suggestion was to build a shared library as fundament for both, kicker app and MeinVerein app. So we did. The kicker app was build by using Fragments. Basically every screen is a Fragment, NewsListFragment to display a list of news, LeagueTableFragment etc. Furthermore, we have built this app by usingMosby a Model-View-Presenter library and so we moved all Fragments (MVP View), RecyclerView Adapters, XML layouts, Presenters and Model classes from kicker app into the newly created shared library. kicker app’s git repository was quite empty afterwards.

The vision was to build screens for the corresponding apps by extending from the corresponding Fragment of the shared library that has already his Presenter, his RecyclerView Adapter, his XML layouts and so on. Customization should be done by overriding some methods from the shared library’s Fragment, coloring and theming in xml and if required provide a custom dagger module to inject non default app specific components via dependency injection. The iOS team did the same with their UIViewControllers etc. In a nutshell: We built a prefabricated house where we can specify the color of the walls.

The problems of our prefabricated house

Thanks to the shared library we were able to ship MeinVerein app within a few weeks. Everything was good. But we noticed the first symptoms: We caught ourselves programming 90% of the time in the shared library project even though that the actually code we were writing was kicker app exclusively and not even used by MeinVerein. But we ignored those symptoms: We use proguard which will remove all the unused classes from shared library in MeinVerein app or kicker app respectively, right?

Another example: We decided to put all our model classes and http networking stuff in the library as well, because even if we the backend is not the same (different url) the backend guys said to us that the json data feed will be the same. So it was in the first version of MeinVerein but as time went by and more features has been implemented the data feeds for both backends begin to differ more and more. However, imho the real problem was that all the pitfalls and issues on kicker backend side regarding json feed structure had also been copied to MeinVerein backend instead of avoiding this issues from very beginning since MeinVerein backend was a greenfield. But you know, we had to move fast, so had the backend guys. Today I know: you don’t move fast by writing bad code.

Overall our shared library looks like this, i.e. to load and display a list of news items (NewsListFragment):

From Prefab House to Lego House

Basically we used NewsListFragment directly in kicker app and MVNewsListFragment extends NewsListFragment in MeinVerein app with some overridden methods to achieve our goals. Looks reasonable, doesn’t it?

Unfortunately, the devil is in the detail. While kicker app has advertisement, MeinVerein app has not advertisement because it’s a paid app. Also some features available in kicker App are not available MeinVerein app.

From Prefab House to Lego House

Those are just two of many UI differences between both apps that has been envoled over the time. Sure, it’s our fault because the inheritance hierarchy is incorrect: The shared library shouldn’t provide a NewsListFragment with advertisement and feature XY build in but that should be rather a AdvertismentNewsListFragment extends NewsListFragment in the kicker app only, right? A few months later we started building a new app called “Tippspiel app” also for Olympia Verlag that looks like this:

From Prefab House to Lego House

Tippspiel looks similar to kicker and MeinVerein app. Actually, is uses the same NewsListFragment . Guess what: Tippspiel app has advertisement but not the feature XY. So what actually should NewsListFragment provide? Clearly we should introduce a inheritance hierarchy like this:

From Prefab House to Lego House

So should we put NewsListFragment , AdvertisementNewsListFragment , FeatureXYNewsListFragment and AdvertismenetFeatureXYNewsListFragment into the shared library so that we can choose the right base class that matches the requirements for the app like MeinVerein MVNewsListFragment extends NewsListFragment (no advertisement, no feature XY), kicker KikNewsListFragment extends AdvertismenetFeatureXYNewsListFragment (advertisement and feature XY) and Tippspiel TPNewsListFragment extends AdvertisementNewsListFragment (advertiesment only)? You see, inheritance doesn’t solve that problem, because for each feature you would have to introduce a new class in your inheriatance hierarchy and there are so many possible combinations for advertisement support, feature A, feature B, feature XY etc. Single inheritance as in java doesn’t scale. I have written an blog post Mixins as alternative to inheritance that you may find useful in this context.

But wait … there is more: lately we have worked on the newest member of Olympia Verlag apps called Nordbayern app (another magazine):

From Prefab House to Lego House

So we decided to keep one single NewsListFragment in the shared library that has advertisement support and all features. Our solution for instance MeinVerein which doesn’t have feature XY was that we extend from this super provide everything NewsListFragment but then inject a feature XY component that does nothing (stub) via dagger whereas kicker app uses the build in feature XY component that does something useful. We thought about this as a kind of solution to “remove unused” functionality.

But wait … there is more: When we started to build our prefabricated house (a.k.a shared library) we were under the very naive illusion that now we only have to write one unit tests for the NewsListFragment from shared library once for all apps and also if we would detect a bug in NewsListFragment we would fix it once for all apps. It’ was quite the opposite. Since, every app extended from NewsListFragment but has overridden some methods in the concrete apps fragment, we couldn’t be sure that the bugfix really fixed the problem in all apps and that the fragment is working as expected. Moreover, with every change or new feature we added to shared library’s NewsListFragment we had to fear that this will break things in other apps extending from this fragment. Our daily work flow was something like this: Write some code in share library (~ 10 minutes of work), test that each app is still working properly (~ 10 minutes of work for each app thanks to grade incredible fast build times). So we spent about 1 hour for a code change doable in 10 minutes.

But wait … there is more: until now we only talked about UI related things. But there are some other stuff like Tracking which are tight to lifecycle, one app has multiple tracking for some strange reason, another app uses an advertisement sdk which is also lifecycle aware and you have to call some methods like advertismentView.stop() in Fragment.onDestroyView() etc. Put all those things and dependencies into NewsListFragment in shared lib? It was a nightmare …

The need of a Lego kit

We from the android team decided to take on this problem. Things got completely out of hand and we were slowed down by our shared library which actually should help us go fast. To be fair, when we started with the shared library it wasn’t predictable that we were going so many apps based on this library in the future and that requirements and features were so completely different from app to app.

We started to brainstorm and we made an interesting observation: Actually, writing a Presenter is super easy and can be done within a few minutes. So let’s remove the Presenters from shared library and let every app define have his own Presenters. The same is true for our http library: communicating with a backend with Retrofit just requires to define a service interface with some Retrofit annotations. It takes only a few minutes. Also the “Model” classes like NewsList were part of the shared library. Defining your own Model class for each app is not a huge deal and is also not really time intensive. Obviously, those things (Presenter, http and “model” classes) weren’t the biggest issue, but removing that from shared library pointed us towards to a general solution we really like: Only keep that thing in a shared memory that is time intensive to code over and over again. We came to the conclusion that the most time consuming thing for us was writing RecyclerView ViewHolders and corresponding XML layout files. Time intensive was to do all the lifecycle aware stuff like tracking and so on and NOT implementing a Fragment or Activity class per se.

Once we realized that, the solution was obvious: We don’t want to build a prefabricated house, we want to build a Lego house where we can only take those Lego pieces we really need for that app. In other words: we needed a Lego kit containing ViewHolders and XML layouts and also a plugin mechanism to hook in tracking and advertisement into Fragment’s lifecycle.

So we decided to deprecate the shared library and build two libraries: One containing ViewHolders, one containing plugin components for lifecycle. Another thing was very clear to us:

Favor composition over inheritance

We didn’t wanted to make the same mistake again: a god alike NewsListFragment

First, let’s talk about how we decided to deal with ViewHolder . Well, there is a library of mine which I have build in this context called AdapterDelegates . The idea is simple: defining a delegate for each RecyclerView’s ViewType / ViewHolder which does the job of inflating xml layout and binding the data:

“`java public class BigNewsItemAdapterDelegate extends AbsListItemAdapterDelegate<BigNewsItem, NewsViewHolder> {

 @Override public NewsViewHolder onCreateViewHolder(ViewGroup parent) {    return new CatViewHolder(inflater.inflate(R.layout.item_cat, parent, false));  }   @Override public void onBindViewHolder(BigNewsItem item, NewsViewHolder vh) {    vh.title.setText(item.getTitle());    loadImage(vh.image, item.getImageUrl());  } } ``` 

So we defined all those AdapterDelegates like BigNewsItemAdapterDelegate , VideoNewsItemDelegate , SlideshowNewsItemDelegate etc. and put them in a library.

Now kicker’s KikNewsListFragment looks like this:

@Override public void onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle b){     ...     RecyclerView rv =  ...;      Adapter adapter = new DelegatingAdapter<NewsItem>();     adapter.add(new BigNewsItemAdapterDelegate())            .add(new VideoNewsItemDelegate())            .add(new SlideshowNewsItemDelegate())            .add(new AdvertisementDelegate());     rv.setAdapter(adapter); } 

Please note that the AdvertisementDelegate() is not part of the library, because this one is used in kicker app and but not in MeinVerein app. I guess you know how the code of Fragments of the other apps look like, right? They reuse the same AdapterDelegates, the same xml layouts etc. However, to be really independent every AdapterDelegate has a tiny companion class for holding the required data to be displayed in a ViewHolder as well, i.e. BigNewsItemAdapterDelegate has a BigNewsItem , VideoNewsItemDelegate has a VideoNewsItem and so on.

So at some point we have to map the data coming from backend to this companion object. This is where a MVP based App pays of. This is the responsibility of the Presenter. We transform the “model” into a “presentation model”. Overall the architecture looks like this:

From Prefab House to Lego House

And for the Tracking stuff? Favor composition over inheritance!

interface LifecycleDelegate {   void onCreate();   void onPause();   void onResume();   void onDestroy();   ... }  class TrackingLifecycleDelegate implements LifecycleDelegate {   private String trackingTag;   public TrackingLifecycleDelegate(String trackingTag){      this.trackingTag = trackingTag;   }    @Override   public void onResume(){     Tracking.viewAppeared(trackingTag);   }   ... }  class KikNewsListFragment extends Fragment {   List<LifecycleDelegate> lifecycleDelegates = new ArrayList<>();   { // Initializer block     lifecycleDelegates.add(new TrackingLifecycleDelegate("Kicker app NewsListScreen"));   }    public void onResume(){     super.onResume();     for (LifecycleDelegate l : lifecycleDelegates){       lifecycleDelegates.onResume();     }   }    ... } 

Now we can share TrackingLifecycleDelegate with all apps. Overall it looks like this:

From Prefab House to Lego House

There are some libraries you can use to achieve the same thing: Soundcloud’s Lightcycle and AndroidComposite by Pascal Welsch.

Summary

In my opinion a Lego house is much more flexible and maintainable compared with a prefabricated house. Favor composition over inheritance is the key to build a Lego kit. Obviously, there is no silver bullet to solve that kind of problem. Our Lego house approach works quite good for us, but i.e. our iOS team still prefer the prefabricated house approach even though they have similar issues as we had in our android apps. Somehow that approach works for them (but don’t ask me how). I I guess the important bit is that all developers working in the same team know exactly how one strategy should be implemented and do that consistently the same way. Maybe we from the android team failed at this point. Was it an error to start with the prefabricated house approach suggested by the most experienced developer in our company. No! MeinVerein app has been build with that approach in incredible little time. I definitely would take the experienced developers recommendation again, but not blindly. I have learned a lot, which hopefully will help me with future decisions. What really matters is to learn from the past to build a better future. I think with the Lego house approach we are moving in the right direction. But who knows, maybe in two years I will write a blog post about how bad the Lego house approach was.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » From Prefab House to Lego House

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址