Developing my first mobile app to ~70k users
I’ve had “App development” on my things-I-want-to-learn-list for some time when suddenly I got the chance to do a deep dive and build a project in the process.
This blog post is the aftermath of a 6-month long process where I taught myself react-native and developed my first app for iOS and Android.
I will reflect upon the process of developing my first app without any prior experience and rolling it out to more than 70k users.
Some background information might be useful before I start.
In my daily job as Head of IT at Kino.dk, a cinema movie website and ticket machine, writing code is not something I do on a normal workday. Normally I am a one-man-army, where I flex my generalist muscle in different fields like data analysis, dev obs, development, GDPR, business, and strategy.
So I enjoyed writing some code and doing a deep dive into a specific area for once.
Another benefit was that it was a perfect scenario for me to practice the deep-work methodology and mindset. A methodology for strong and narrow focused sessions of uninterrupted work, that I’ve grown to love and enjoy. I would like to reflect on the combination of stoicism and deep work, but that is for another blog post.
This project was born during the covid-19 lockdown of Denmark (and the cinemas), where my workload was limited, but my urge to spend my time productively was strong.
So I decided to see if I was able to create a new app for Kino.dk while the cinemas were closed.
Maybe I’m a geek, some might even call me a nerd, but I enjoy diving into new tech areas. Skilling-up as they say.
I keep a (pretty long) list of topics I want to take a look at and try out.
The list grows all the time, because tech moves fast. I do use it, though.
Every year I pick a topic (or maybe a few) from the list and then make a plan for how I can get my hands dirty with this new technology.
I then create a list of tasks or elements, everything from project ideas, tutorials, blog posts, and so on, actually just picking up resources.
They will be listed in a sensible order and I just plow through them one by one, when I have the time and energy. Eventually, I end up with some kind of knowledge.
At some point, I will lose my patience and start a project. This usually happens after I’ve researched/learned the most essential parts.
The whole idea behind my “plan” is that I always know what’s next.
I don’t need to start by figuring out what to do when I sit by the computer, I can always just sit down and start right away.
The progress is very clear and visible.
The goal is to constantly be moving forward. Consistency is the key to my methodology. No jumping between topics that are too far apart, no randomness in the steps, just a straightforward and clear path.
Ouch, this section became longer than expected, but don’t worry, we are getting to the good stuff.
Planning; limits and requirements
I work by a simple principle. Plan what you do, do what you plan.
So the first thing I do when I try to learn something new is to read the: https://reactnative.dev/
Usually, I read the documentation to get some kind of overview before I start setting up the dev environment and such. Once that is set I re-read the documentation with a focus on the things I know I need and try out and tinker with the sample code/tutorial/introduction.
Before I started the actual project, I stitched some rough sketches or wireframes together.
The goal for the project was clear;
I want to make the fastest way to a cinema ticket, with as few interactions as possible.
When you go to the cinema there are 3 parameters in play. The movie, the cinema and the date. If I can get the user to select either one of the 3, I can easily display a good overview of the other 2 options.
Therefore I decided to present the most popular movies and the cinemas closest to the user on the home screen. So the home screen can be quick navigation for the users to either the cinema or movie he is interested in. Pretty simple, huh?
Next up is the movie’s screen. Here I want to present a list of movies as a kind of overview. Nothing new in presenting movies like this.
Since the list tends to get a bit long, I do want to add some kind of search on the top, to help the users who are trying to find a specific movie.
Touching a movie poster will lead to the movie screen, with a more detailed view of the selected movie. On this screen, the user will be able to view the trailer, read more about the movie, see showtimes, crew, and order tickets.
The vertical bar with the dates is what I think is the easiest way to present selectable dates. The goal is that the user should be able to touch the date he wants to see the showtimes and they will update. Default is today’s date.
A similar structure for the cinemas with an overview screen and a detailed view.
The order of the cinemas is important, though. By default, the cinemas are sorted in alphabetical order, which is not very useful.
So to improve the user experience, I figured I should prompt the user to let me use his geo coordinates. I can then use them to calculate the distance from the user to each cinema and sort the cinemas with the nearest first.
The user can select a cinema and go to the cinema screen to read more about the cinema, see the program, showtimes, and order tickets.
The last thing is the user login screen. Users should be able to log in to autofill the form with the info that’s needed when ordering tickets.
For now, I want to keep it simple and just show the user’s purchase history.
Besides the different screens, the observant reader might notice the bottom navigation in the app. This is nothing new, actually pretty standard navigation between the different screens.
Let me know if you think, I should pursue a career as a designer, ok?
Initial setup and the first lines of code
Now that I have some kind of idea of what the app should look like, I can start setting up my dev environment and start the actual coding journey.
Following the documentation, setting the development environment up on my mac was pretty straightforward. https://reactnative.dev/
Starting a new app with npx react-native init KinoApp and I had a very basic structure of the app ready.
I am a bad designer, I know that. Sometimes I miss details, another weakness. Therefore, I like to help myself with some design decisions. So I started out making a “Theme file”. It contains a lot of reusable code for the UI. It would help me keep the app design consistent across all screens and components. This is just a habit I’ve got from web development.
So I created a range of fonts with different sizes, widths, heights, and paddings. I should note that I made the widths and heights dynamic, so they are calculated on the fly to make the best fit on every screen size, but still consistent!
Then I created all the different screens and started wiring them together through the navigation.
Well, I knew about stack navigator, bottom tab navigator, and drawer navigator, but making them work together was the hard part. Apparently, I had to nest a stack navigator inside the bottom tab navigator. For experienced developers, this might be obvious, but nevertheless, it took me some time to wrap my head around it.
It is pretty different from normal web development, because the navigators have their own history, so it’s very easy to mess up the “Back” button.
Confused? Me too!
It is a bit technical, so I will leave that to another post, where I go into detail with the technical decisions.
Somehow, I managed to get the navigation to work as intended, so I could navigate between the different blank screens on my simulator.
Next up: let’s fetch some data into the app and start working on the UI and design.
The data used in the app is fetched through JSON feeds and API endpoints from Kino.dk. I wanted to pre-fetch as much data as possible, to make the app fast and with a native feel, once it was loaded.
The data had to be stored somewhere and here react (and react-native) has a wide range of options. Because this is not a normal server-side-rendered website, this is different too! Long story short, I decided to go with the Context API and not the very popular Redux library. I did put a lot of thought into this, keep my limited experience in mind, this did require some research. The reason why I went with the Context API, was that I had no need for very complex data structures, but even more important, I tried to favor the “out-of-the-box” solution. If possible, I tried to avoid using external dependencies. I believe in control over code and with limited dependencies. Hopefully, I did myself a favor when maintaining the app.
I set up a data-fetching component, which will run every time the app “wakes up”, and on initial load. An array of movies and an array of cinemas are fetched and stored in the context. I remembered to wrap the main component with the contexts, so the data is available everywhere in? the app:
This might seem strange for those who are new to react (native). It was strange to me at least.
Let me clarify; The CinemaProvider is the Cinema Context component. It fetches, updates, and exposes the cinema data. Same with MovieProvider, except it’s for movies. The AuthProvider is used to handle the login of the user.
Then you see the FetchData component I mentioned above, this is a component that pre-fetches the data and stores it in Context.
This means that the data can be available inside all components, where the context is imported.
The last thing is the BottomTabNavigator. Inside this is where we find the main app component, which contains the app.
So to make the data available in the app, we have to wrap it around the app component. Pretty simple and pretty strange.
There are so many resources on this topic so a simple google search will enlighten you far more than my silly post.
Finally, some data to work with. Let’s start looking at the Movies screen, the screen that has a list of all the movies.
React-Native has a few components that you quickly will get familiar with if you start coding. Let’s shortly inspect a few of the most common ones.
- The <View> component -> This is very similar to the <div> tag we know and love from html
- The <Text> component -> This is a component that contains text. Surprised? It is similar to tags you know from html: <p>, <i>, <h1>, <h2>, <h3> and so on.
- The <Image> component -> This component can display an image.
- The <TouchableOpacity> component -> This is a bit similar to a view but will make the “area” touchable, which means we can have an onPress function on the component that will do something. Can be used to navigate the user somewhere.
- The <FlatList> component -> This one is interesting. This is a list of items. It takes in an array and loops over the items and displays them. These items can be anything… images, views, text, or a combination. Pretty handy, huh? In our case, this component is ideal for our list of movies.
- All the components are well described in the documentation.
So I started out creating a FlatList on the movies overview screen. It will take in the array of movies from context and the loop through it and for each item display a poster image and a movie title.
Sounds simple, right?
numColumns is set to 3, so the data is present in 3 columns. The key extractor is an ID that the FlatList needs to identify for each object in the array. Notice the renderItem function? Good! It’s important! It calls a function called Item and takes an item as a parameter. Clever naming, right? — I didn’t come up with it, I was inspired! Nevertheless, the Item function is responsible for presenting each item in the array.
So the function looks something like this:
The Item function is where I continued to work with styling and adding the option to navigate the user to a screen for a single movie. One strange thing, which is actually not strange at all, is that you need to add some kind of width and height to the image, otherwise it won’t be visible. Again, thank you documentation!
I will not go through the cinema overview page, since it’s very very similar. Contains a list of cinemas. You can see the “final” result here:
Everything except the navigation has been pretty straightforward until now. Next up was to navigate the user to the movie screen, when pressing a movie poster. I still don’t understand why this very basic functionality was so difficult for me, but the combination of the stack and bottomtab navigator created some issues. When I face issues like this, I combine reading on StackOverflow, checking blog posts, and just trying a few things out. A deeper understanding of the problem is required in order to get enough information to make a detailed search for a solution.
To sum up, I tried to keep the structure simple, with a screen listing all movies and one with all cinemas. From there the user can navigate to the movie or cinema and order tickets.
In addition to the navigation, I added a search bar on top, where the user can search for a movie or cinema instead of scrolling down. I called it search, but it’s actually just a filter functionality!
When ordering a cinema ticket, there are 3 variables to consider: The movie, the cinema, and the day. We need the user to choose at least 2 of the 3 before we can present a view with showtimes that is not too overwhelming.
The movie and cinema profile is the last step before selecting a showtime and purchasing a ticket. Therefore, I wanted to keep the information to a bare minimum, so it is very obvious how and where to tap to order a ticket. If the user wants further information, like a review or list of actors, a little more work is required.
I hope to satisfy the majority of the users with minimal initial information. Of course, information like a movie title, genre, and so on must be present and easy to see.
The result currently looks like this for a movie:
Some fun featured I decided to implement, was to fetch colors from the movie poster and use them throughout the movie profile. So all the movie profiles will look different depending on their poster. Another good thing about this was that I didn’t have to figure out some kind of cool color scheme..
I am fetching a primary background color, a primary non-background color, and a secondary color. These are used throughout the different elements to hopefully create a more unique experience for each movie. This feature does create some problems, though. One of the issues is that it works well for some posters, but others will not be that great. And I have no control over this. But somehow I’m still responsible!
From a technical point of view, this feature does collide a bit with my hopes and dreams about load speed. Keep in mind that each movie screen will have different colors, so these colors must be fetched on the fly, without making the user fall asleep in the process.
For the cinema profile, I’ll use a very similar design, but without the unique colors. The information presented is pretty similar and I want to keep some kind of consistency in the design.
One of the tougher design decisions, I had to make in the process, was how to present the showtimes. I was (of course) inspired by other apps and figured that the user most likely know which day he wants to go to the cinema. So we can keep the view to be showtimes for 1 day at the time. I tried to follow some conventions by choosing a vertical list that works as a date picker. Selecting a date will fetch showtimes data for the movie or cinema and present it.
When selecting a cinema, you can see this particular cinemas program for the selected day:
When you select a movie, you can see the showtimes in the different cinemas for the selected day:
This way, you will get an overview of the “entire” program for that day. So either the user selects a cinema or a movie and sees the showtimes for that day.
Animations and smoothness
How do we judge quality? Whenever something is aesthetically pleasant?
A well-timed and carefully selected animation can do wonders for how the app “feels”.
It’s an art and something that I by no means master. I do know if it was successful or not. But hey! I figured out how to do animations!
I have added a few animations to ease the transition between screens and on the loading of screens.
If you have tried the app, you might have noticed how the images slide back and forth when you go from the movie/cinema overview and to a movie/cinema. I combine animations and navigations to try to make the transition smooth.
Also, I do a fade-in with a slight delay on some elements on the movie profile, like the play icon for the trailer. It “drops in” shortly after the screen is loaded. Why? — Well there are a few reasons for it. One is to draw some attention to the button, if you press this, then you can watch the trailer. Another reason is that 95% of all movies will have trailers, while some won’t. So I have to do a check in the data to see if the movie has a trailer or not and I don’t want to bother the user and make him wait for that check.
Another small and maybe useless animation is when the user presses either a button or poster. It will mimic a real physical button being pressed down and go back up.
It’s a small thing, but it all adds up and contributes to the overall touch and feel of the app.
Challenges in the process and how to overcome them
One of the issues that took me the longest to figure out was the data structure and how to re-arrange it to meet my needs.
Next up was the geo-location, where I fetch the user’s GPS coordinates, pair it with the cinemas coordinations, calculate the distance, and sort the cinema list by distance to the user, with nearest first.
Simply displaying the showtimes for a movie, for each cinema was a challenge, too.
When users select a showtime, a webview opens because there are no API endpoints for the last few steps of the ticket purchasing flow. So this was my only option and the biggest weakness of the app.
Figuring out how to auth the user and pass the user to the webview was also something that took me a while to make work.
Data structure and restructure
The data structure was one of the first challenges I faced. To understand the issues, we need a quick overview of the data. First off we have a list of cinemas and a list of movies. Each movie will have a list of showtimes, queried by date. So we can fetch all showtimes for a movie on a specific date. The showtimes is an array of arrays, where they are grouped by cinema. It makes sense, right? And then there is a similar structure for the cinemas, where you can get all showtimes for that cinema on a particular date, grouped by movies.
Example for a movie:
So the structure is an array of cinemas and under each cinema, there’s a showtime object that contains an array of showtime objects. Mind the “movie_version_id”, this is a reference to if it’s a 2D, 3D, or something else. I wanted to group the showtimes by version. So I wrote a small function to restructure and re-arrange the array:
Well, it’s easy to understand, showtimes are grouped by movie_version_id, which is the version. So all 2d movies will be grouped under the same version label. Perfect.
And the new structure:
Geolocation was another issue. Keep in mind, my goal was to make it as easy and fast as possible to get a cinema ticket. Even though Denmark is a small country, it’s very unlikely that people will travel through the entire country to go to a specific cinema. They will almost always want to go to a cinema near their current location. So I implemented the option to fetch the user’s geolocation and pair it with each cinema location and calculate the distance from the user to each cinema. Once the user opens the app, he will be asked if the app can get access to the GPS to determine his location. The location data is not saved, but only used in real-time to calculate the distance to the different cinemas. Let’s pretend the user said ok and then some coordinates are returned to us to use. I wrote a function that will calculate the distance between the location of the user and another location. Then looping through all the cinemas, asking this very question and using each answer to add a “Distance” parameter to the array of cinemas. The distance (big surprise) is how far the user is located from each cinema. And then sorting it with nearest first.
How the heck do you calculate the distance between 2 sets of coordinates? And get the answer in kilometers?
Well, thank you internet!
I feel like I want to write more about this because I spent some time researching to understand what is going on and it’s fascinating. If you want to talk about this, let me know. I’ll buy you a beer that you can drink while I talk nonsense about this.
Then I made an updateCinemas function in the Cinemas Context.
Voila! The cinema’s array now has a distance value and is now sorted by that. So the cinema that is closest to the user will be the first one on the list.
Notice the comments? — Yes, I’m a nice guy like that!
GeoLocation for movie showtimes
Next big issue; How am I going to use this on the cinema overview screen? I need it in the showtimes for each movie, too, so the sorting of cinemas is consistent across the entire app.
Well, diving into the way I display the showtimes for each movie, I wrote a function that handles the group by showtimes issue but adding the distance value to each cinema. Because this distance value is only in the cinema list, not the list for the movies showtimes. Luckily I could match on the cinema IDs since they were present in both arrays and then merge them.
Finally, it looks like this:
Each time showtimes are loaded for a movie, data is restructured to have the right sorting and distance value on cinemas.
Now both the list of cinemas on the cinema overview page and the cinemas on the movie screen showtimes are sorted by the distance parameter.
Displaying the showtimes
All of this leads me to the issue of displaying these showtimes. So we talked about FlatLists already. They are great and perfect for this use case. Let’s pretend we are looking at showtimes for a movie. The data structure is sensible, because for each movie there is an array of cinemas, and nested inside this cinema’s array there is an array of showtimes. But I grouped all the showtimes by their version and so it’s a 3-stack deep nested array before we get the actual showtimes.
This calls for nested FlatLists! The first FlatList will contain all Cinemas. Then for each cinema, a nested FlatList is rendered, this one will contain all the versions. And finally, inside each movie version, yet another FlatList is rendered, to display the showtimes for this movie version at this cinema. Phew.
BUT PERFORMANCE? Well, actually it’s not a problem, since we use the keyExtractor ID to index the items AND they are lazy-loaded. It is interesting, though, because each time a user selects a date on a movie, all this will happen, including grouping the showtimes by version, merging, and sorting of the cinemas.
Remember, the order of the cinemas will vary depending on the user’s location. I wish I could query the API with longitude and latitude coordinates, and it would then return a perfect structured array of the items, but that would make this section both very short and very boring.
NO API end point
The biggest challenge, issue, and flaw in this app is how I handle what happens when a user presses a showtime and wants to select seats and buy a ticket. Kino.dk, the website, has some deeper integrations to the different ticket systems, which makes it feel like a part of kino.dk. There are no API endpoints for this part, so the only option I had was to open a webview on kino.dk targeting the selected showtime.
This is by no means the best implementation, but the only current option I had. My plan for Kino.dk was to extract the ticket system logic out to an external service and then I could hook both Kino.dk and the app to that service and provide a true native app experience. Without having thought this through, there could still be some issues. I could make the deeper integration with the app and ticket systems/middle layer, so the users can select a number of tickets, select seats, etc, but how did I handle the payment? I had to use the cinema’s/ticket systems payment gateway since we are selling tickets on behalf of the tickets system or cinema. So it is not Kino.dk’s money, but the cinemas’.
It would require Kino to have its own payment gateway that will distribute the money to the cinemas. I am not sure it will happen.
If you got any good ideas on how to solve this, I would love to hear from you!
Another twist with the webview was that I had to fetch cookie and session data and pass it to the webview to make sure the user was logged in.
The webview is just an in-app browser that will show the mobile website. The user will get validated/logged in with the right cookies. These cookies or strings are available to me through the authentication endpoint.
What happens behind the scenes when the user login, is that some strings (cookies) are returned from the API and saved on the phone through AsyncStorage. These strings will be passed as cookies to the webview, to validate the user and log him in, so he doesn’t have to add fill out redundant data.
Luckily there’s a react-native library (react-native-cookies) to make this easier.
One thing that surprised me was something the cool people call the 80 / 20 — rule. It means that you can do 80% of the project in 20% of the time. That leaves us with the last 20% that will take 80% of the time. It is not entirely true for my project, but I experienced a lot of missing pieces just before the release. And that is without talking about the actual release, which is a huge pain.
I eventually ended up reaching out to a freelance React Native developer, who could help me with some code review and speed up the last bit of the process.
Thank you, Stefan! You are amazing and I admire your patience.
If you need a react native developer, I can recommend this guy. You can find him here: https://github.com/Hyllesen and take his course:https://www.udemy.com/user/stefan-hyltoft/
The whole deployment of the app to AppStore and PlayStore should be a chapter by itself. It was not difficult, it just took a long time to make the images fit, fill out the information, and all that.
Lessons learned and key take-aways
I have learned a lot in the process and there are a lot of key take-aways that could benefit anyone who might consider similar craziness. When I started out, everything was a bit confusing and I had to look up almost everything. But as I progressed, some of the stuff somehow got stuck in my brain, so I was able to operate more freely. The more you know, the more you know that you don’t know. And this is true. But while you build knowledge and insight, your solutions will also become better. And maybe the most important thing when expanding your knowledge is all the ideas and solutions that pops up. Alongside the knowledge expansion, a range of possibilities will become clear and it’s in this space my innovation thrives.
Even though the last bit of this article was full of problems that I faced in the process, I finish the project with a feeling that this is doable for anyone. I am no genius, just methodical and very stubborn. I have a lot of experience in teaching myself new programing languages and tech stuff in general. I have developed a method (that works for me) for learning new things, which means that I can fairly quickly pick new things up. I keep a list of things I want to dive into and every year I pick a topic (like React Native), make a plan and start my deep dive. It’s just a hobby and the pressure is off, so I can do it out of interest and not out of need.
I made a checklist with a few notes on what to consider;
- Deploy to AppStore and PlayStore early
- Test on physical devices all the time
- Screen sizes -> they vary. So check on different sizes
- Some users have extra big fonts. That will break the design. For me, it was not an option to just force the size of the font. I wanted the design to adjust to users who needed bigger fonts. It took me a while
- Tracking. Think about it from the start. Implement it, test it and then test it again
- Handle what happens if the user is offline
- Some kind of error message to the user, if the API call fails
- Be aware of what happens if the user puts the app in the background, but without closing it. (I used a method where data is refreshed upon making the app active again)
- How do you want to present the app in AppStore and PlayStore? — Creating those images does require some work and if you want it to look good x2 that work
- How consistent is your data? Can you count on every attribute being there? If not, handle it
- There is no caching on the default Image element in react native
- Animations are fun and nice, but they should be used with care
- Plan the component structure
- Follow conventions
I like to work in small, but never-ending iterations, where I develop an MVP and then take small steps based on a combination of data and user feedback. So far I have got extremely good feedback and some great ideas for the next steps. There has been some bad rating and reviews in the store, though. Annoying as it is, they are all related to the webview implementation, the big weakness of the app.
I have actually already released a new update, where users can add cinemas as favorites. They will then be listed at the top of the showtimes list for a movie and on the cinema overview. The reason why this is the first feature was that I could see that 20% of the users did not allow geolocation. That means that they will get a list of cinemas in alphabetical order, which is not very useful. And I want to keep the focus narrow and centered on how to make it as fast and easy as possible to order a cinema ticket.
What else is in the backlog? I have a list and it is pretty long and without any filter.
I do know for sure that I want to add news to the app, too. Kino.dk is a ticket sales machine for cinema tickets, but it is also Denmark’s biggest movie media. So supporting that part of the business would be a natural next step.
I also want to give the editors more freedom to make an impact on the app. That could be promoting a certain movie or communicate with the users either through articles or competitions/polls. How? I have no idea.
Another idea I consider is to add a view/screen where the user selects a date and then a list of cinemas and their movies (and showtimes) will be displayed. It should answer the question: “What’s in the cinema on Saturday? Something worth watching?”
Did you really read all this? Phew, good job!
Now that the app is up and live, I can show off, brag and all that. There are currently around ~70.000 users on the app and the number keeps increasing without any marketing.
Honestly, it was pretty hard. At times it felt like a mental marathon. I started with no idea at all how to do this. Doing small baby steps, consistency, no pressure, and lots of time were the key ingredients. I was able to work my way through it and tackle each issue with a calm focus. I did this project alongside my normal job, but on shorter hours. I tried to code at least 1 hour each day, and it took me around 5–6 months to learn and build.
It was fun to dive into app development and figure out how to build an actual app. And I will recommend you to give it a shot if you have the time and interest. I believe that this is doable for anyone who can dedicate the needed time and focus.
I know for sure that I do not want to be a mobile developer full-time. It’s interesting and fun, but I would get tired and I need to dance in the space between tech and business. Not only in one of the 2 camps.
Thank you for reading the article! And feel free to leave a comment or reach out to me if you have any feedback, question or want to chat about development and IT.