Hackathon without hacking

The tech story from #refoodgee during project #hackweek15 #refugeeswelcome

Back in September the whole HeyJobs (previously Memorado) team had 4 awesome days of Hackathon.

The goal was to build 4 apps in 4 days that can help refugees in their daily life. In order to achieve this ambitious goal, we’ve divided into 4 teams with 5 people in each. Our team decided to work on the app that we called Refoodge (the app is now both on play store and github). The main task of the app is to bring refugees and locals together by cooking and sharing a meal together

As the whole event is described in details here, in this post I would like to focus on the strategies me and my teammate (Igor Filippov) chose for development.

Since it was two of us writing code, we decided to have a development planning phase to agree on how the set of features should be implemented. Pretty quickly we chose libraries and services that fit the project and started thinking about how the architecture should look like. After a short reflection, we came to a conclusion that there is no need to change the approach we normally use.

If the goal is to release the app in the Play Store, then it is not prototyping anymore. If we are not using any new technology, then it is not about gaining new technical knowledge. Moreover, the goal was to create an app that is helpful to refugees, so for us it meant not hacking but creating. That is why our internal goal became the fast implementation without sacrificing the code quality. In my opinion, the approaches described below helped us to achieve it.

Identify the layers

The first step was really simple. It was absolutely clear that we won’t have all the code in one Activity (not even in two and fragments do not help here). The code was separated in three layers: domain, data and presentation. The domain layer was intended to hold business models and repository interfaces. The implementation of the interface was supposed to go into the data layer. Finally, all the UI was planned to be put in the presentation layer.

Define the interfaces

The next step was to concentrate on the domain. We outlined the models that were required for the main app functionality(sending invitations), defined repository interfaces and created a draft of interactors to be used in the presentation layer. Repository interface described the main operations with the core models of the app. Interactions in their turn were using repositories to control the business logic and were intended to be used in the presentation layer.

Organize dependencies

In order to have a clear understanding of how class dependencies are organized and produce maintainable code, we excluded any possibility for singletons and made every class use constructor injection. As DI framework we chose Dagger. Though Dagger (version 2) was relatively new to us, we managed to organize modules and dependencies in way that allowed us to easily control the communication between layers. For example, switch repositories(in-memory/backend) via a build flag.

Implement the interfaces

After the planning part was completed, we’ve allocated the tasks and started writing code. Since the development time was scarce(we had 2,5 development days) we decided to work on a single branch. In addition we tried to commit and push as often as possible to avoid large conflicts. Actually the were just a few. After clear layer separation and interface definition we were able to work on the same features but in different layers without conflicts.

Postpone backend solution

In order to have the data stored on the backend, Parse was chosen as the best option to fulfill our goals. Though it looked as „all-you-need-is-backend-in-the-cloud“ solution, we decided to come up with in-memory implementation of our repositories first. Actually there were several reasons for doing this:

  • Firstly, we wanted to have a clickable prototype as fast as possible, so we could adjust features in the early stage of development. In-memory implementation was obviously faster and allowed us to avoid any unforeseen problems with the technology we didn’t know.
  • Secondly, we didn’t want the UI implementation to depend on backend communication. Our internal requirement was to speed up the work on the UI. For example, while working on the invitation details screen we didn’t want to wait for login, then for the list of invitations and then for details. Also we didn’t want to pollute our presentation layer with the mock data which could have caused bugs. Instead we preferred to pass some predefined invitation id to details screen(activity), retrieve it from in memory repository and display details as from the real backend.
  • Finally, the internet connection was extremely poor. So we had to have some fallback for the presentation day in case the connection is slow or dead. In memory repository allowed us to have some predefined values and present the complete app functionality if needed.

Add tests

Our initial intention was to add tests at least for the business logic. The only reason I am placing this paragraph here and not before the implementation part is because we failed. Pretty quickly we realized that the time limit given for the task won’t allow us to add tests. As a minor justification — the structure described above allowed us to unit test each class in isolation. Moreover, our code was not paralysed by Parse library which comes with singletons and active record pattern. With repository interfaces, Parse was clearly abstracted from business and presentation layers. So skipping tests was probably the only decision we wouldn’t make in a real project.

Conclusion

After 4 days of planning, designing and developing our team was glad to present an app with limited but solid feature set and an outstanding design. Of course, we were not able to avoid hacks as we were approaching the deadline. On the other hand, there were not that many of them. Even though I wouldn’t say that this was the best code we could have produced, the decisions described above allowed us to achieve the following:

  • Work on the same features without conflicts
  • Create testable classes which followed single responsibility principle
  • Work on UI with real data without waiting for backend implementation
  • Have project structure which allows to easily identify code related to a specific feature
  • Isolate 3rd party cloud service(in our case Parse) by introduction of the repository abstractions
  • Create an app in a very limited amount of time without having a dirty code base polluted by hundreds of hacks
  • Reduce number of unexpected bugs hidden deeply in the implementation

In general, we were very glad to prove that planning the architecture and agreeing on interfaces in advance led to properly structured and maintainable code even in such a limited scope and time. Something that not all real world projects have.

We are always looking for talented developers, UI/UX designers and product managers, check out the latest openings here

Konstantin Shilovskiy
Android Developer HeyJobs