Earlier this summer, I, along with three other developers from Nagarro Digital Ventures—Nelson Glauber, Michael Kral, and Brian Thomas—held a meeting to consider initiating a Flutter project as a skill-building exercise and for fun. All of us were mobile developers, two Android and two iOS. We were all between client projects and had previously experimented with Flutter. A few of us were football fans, and the FIFA World Cup was coming up, so at the end of the meeting, we decided to build an app to track the standings of the World Cup.
In this article, we discuss the Nagarro World Cup app that was built: its features, architecture, and some of the engineering choices that went into it.
The Nagarro World Cup app offers the following features:
1. Provides three different views into World Cup data:
- first, a Standings tab—showing detailed team statistics broken down by group,
- second, an All Games tab—showing the date, time, and current score,
- and third, a Finals tab—a tournament bracket showing scores and upcoming matchups.
2. Offers a detailed view of each team, giving individual player statistics.
3. Shows a detailed view of each game, updated frequently when games are in progress, showing: 1) a timeline of important game events, 2) a statistical comparison of each team's performance, and 3) a line-up of the starting players.
4. Allows users to make and share their own predicted World Cup results.
5. And lastly, it allows users to follow their favorite teams. They will then receive notifications of upcoming games and important in-game events, such as goals scored.
The app opens on the Standings tab. This contains a list of the eight groups in the knockout rounds. When a group is expanded, the most important scores for each team—points, wins, and goal differential—are shown. If the user clicks See More Details, other statistics, such as losses and goals for/against, are shown.
The app navigates to the corresponding team detail screen if the user clicks on the logo of any team.
2.2. All Games
The All Games tab shows a chronological listing of all games, filterable by the group. Each matchup shows the date, time, and score.
The app navigates to the corresponding game detail screen if the user clicks on any matchup.
The Finals tab shows a tournament bracket. At the beginning of the World Cup, the bracket will be empty, and as the World Cup progresses, it will fill up. It shows upcoming matchups if known, and scores for current and completed matches.
If any bracket pairing is clicked, the app will navigate to the Game Detail screen for that game.
2.4. Team Detail
The Team Detail screen is reached by clicking on a team's logo in the Standings tab. This screen contains a list of all players on the team, with their jersey numbers broken down by position. If the user clicks on any player, relevant player statistics are shown, such as minutes played, flags drawn, and goals scored.
2.5. Game Details
The Game Detail screen is reached by clicking on a game in the Finals tab or in the All Games tab.
The Game Detail screen has three tabs:
- The first, Summary, contains a timeline of game events—goal attempts, goals scored, penalties, etc.—as they unfold.
- The second, Stats, contains a side-by-side comparison of each team's in-game statistics, comparing shots blocked, shots on and off goal, fouls, and so forth.
- And finally, the third tab, Line-ups, shows a diagram of the pitch with jerseys placed to show players and their positions on the field at the start of the game.
The fantasy Prediction maker is reachable through the Options menu. It contains a Groups screen and a Finals screen. In the Groups screen, users can predict the winners and runners-up from the knockout rounds. On the Finals screen, the group selections will populate the first column of an empty tournament bracket. Then users can click their predicted winner from each matchup to fill the rest of the bracket.
Users can save their predictions at any point. They will receive a link to their predicted bracket, stored as an image file, which they can share with their friends and family. Saving and sharing predictions requires user authentication.
2.7. Follow a Team
The follow a team feature is accessible through the options menu. Here, users can select teams they wish to follow. Notifications will then be pushed out to alert the users when their teams have upcoming games and when there are important in-game events such as scored goals. This feature requires user authentication.
We chose RapidApi's Football API for our source of World Cup data. This is a rate-limited API that charges by the number of requests. We did not want to spend much on it, so we use Firebase as a middleman rather than using the API directly. This involved writing and deploying a Firebase back-end function to call the API at a frequency of once an hour to update the data. Team standings and statistics are saved in Firestore. Similarly, the pictures of the team members go to Firebase Storage. Firestore and Firebase Storage are live databases, so when changes occur in the data, front-end instances will update automatically.
Many of the app's features require no authentication. For example, standings, statistics, game times, and scores are all public information accessible to non-authenticated users. However, the app has two user-personalized features that require authentication. The first is Follow a Team: we save the user's team preferences in Firebase, and the back-end uses this information to know when to send out notifications to each user. The second is the Fantasy Prediction feature: when a user wants to save and share a prediction bracket, the bracket is saved as a PNG and associated with the user’s ID in Firebase Storage. Then the user is given a link to the image, which can be posted on social media or shared with friends and family.
Our engineering decisions on the front-end were driven by our central goal of complying with Flutter development best practices. For this reason, we chose the BLoC architecture to handle the separation of concerns—it is currently the most popular Flutter architecture for large-scale app development. BLoC is an architecture and state management solution in one, having similarities with other mobile front-end architectures such as MVVM. Therefore, the code for each main feature of the app is spread across, at minimum, four Dart files. One file lays out the UI, one defines events, one defines states, and the last defines the BLoC (which stands for Business Logic Component). Events are sent to the BLoC upon user interactions or messages from the back-end, then the BLoC emits states to the UI layer in response. When the UI layer receives a new state, it rebuilds the screen layout to reflect the new state of the app.
In Flutter, every widget has a build method, and every build method receives a BuildContext parameter which is passed down from widget to widget. This structure allows using a provider pattern for dependency injection, where dependencies can be provided near the top of the widget tree. The widgets requiring those dependencies can access them by searching the context, which will deliver the nearest upward instance of that dependency that exists in the widget tree. This is how the presentation layer communicates with BLoC and repository layers.
To conform with best coding practices, we use the standard i18n package for internationalization. Similarly, we use the go_router library to handle app navigation. With Flutter, it is possible to handle all navigation simply by switching between widgets, but go_router allows our code to comply more closely with front-end development practices seen in other domains such as the web, and it also gives us the ability to use deep linking. We also use the JsonSerializable package to reduce the amount of boilerplate code needed in our model code. It uses code generation to add functions to our model files that are necessary but would otherwise be repetitive and tedious to code by hand.
4. Lessons Learned
We faced several challenges in making this app, not least of which was that team members were occasionally pulled away when billing projects came up.
Regarding technical challenges, we encountered only one bug in Flutter itself, and found a workaround. After a user selects to create a shareable link to their fantasy predictions, the app takes a screengrab of their predicted tournament bracket. We found that rasterized images—PNGs and JPGs—were not being rendered in this screengrab due to a known bug within the Flutter platform. This was a problem because all team logos delivered by the RapidApi Football API are PNGs. We were able to work around this problem by finding SVGs of the flags from another source and using them as local image assets rather than using RapidAPI’s logo images.
We had hoped to score the football predictions to make a prediction competition, and we found examples of how other fantasy football leagues did this scoring, but we needed more time to implement it. Since our main goal was to build our Flutter skills and prediction scoring would be more of a back-end task, we chose to put it on the back burner for this iteration of the app.
Special thanks to David Anta, our project designer. We started out as a team of only engineers, and while we were able to make do at first, our app's UX soon became unwieldy. Finding a designer with enough time to help us was not easy, but David generously stepped in to draw up some better designs for our app, and we have since reskinned the app to match those designs.
In conclusion, we have written a small app to track the World Cup. It was released for internal testing by Nagarrians on November 20th, 2022—the day that FIFA World Cup 2022 started. If you wish to try out the app, please do. The locations for the two mobile platforms are given below.