Code Conf - The long journey
This is a story of creating Code Conf app. Take good coffee or tea. Sit well and I hope you will enjoy it. It was awesome retrospective to me.
It was few weeks when dust after launching my first app Family Graves Map has settled. I started to think about an idea for my next project.
I have had few candidates onboard but one has been a leader before the rest. At that time when I was releasing my debut iOS app, I was learning iOS development for only 6 months. I was learning a lot from different sources: articles on the web, Stanford C193P course, books and conference videos. I have found that the last category of my knowledge source as the hardest one to discover and find. I didn’t know what are names of iOS developer conferences, I only found a few of them at the start of my iOS developer journey. That was the case … there are lots of people like me who want to learn, get inspired by others but have no idea where to find this knowledge. Maybe they find as me just one or two conferences and then struggle with not knowing the others. This idea was so strong in my mind that I decided to go with it and tried to make and app from it. I created some drafts on paper with main features, how I want it to look, what data I need to have and how the user could interact with this data.
At first, this looked like an easy project. Three main data models, simple API and I will end it on 02.02.2020. I thought this date was great to launch this project. But this was on November 10th 2019 when I committed first lines code for API. Life has written its own story during this project.
If you are for the first time on my site, maybe not yet know me and have not read my bio I will share with you one detail. I am a full-time Pharmacy Manager. My daily job is far away from coding. I was coding after hours, on weekends, sharing with this project my family time.
UIKit or SwiftUI?
While making this first drafts I have decided to try to write iOS part of this project in brand new SwiftUI framework. After reading lots of resources I felt that this will be a good idea to try. In a few days, I was able to create the first static draft of almost the whole UI I had described then on paper. It was easy, it was fast, and it gave me a lot of fun to do it. I was sure that with no so huge experience with UIKit I won’t be able to prepare all these things with the same amount of time and struggling with storyboards and auto layout.
Decision number 1: SwiftUI no matter what will happen next.
Backend API framework
iOS app was just one side of my project. The second one which I knew will be required was backend API. I was programming occasionally in PHP a few years back in Symfony framework and done medium size backoffice app in it, but I wasn’t sure if I want to learn one more time “new paradigm” with the current version of Symfony and PHP. Back around WWDC 19, I have bought a set of books from Paul Hudson named “Swift Platform Pack”. It contains books “Server-Side Swift” Kitura and Vapor editions. I thought that writing my backend in Swift will be a lot easier than learning at the same time PHP for backend and Swift for iOS part. After research, I decided to go with Vapor. I deep-dived into docs, above mentioned book and started my first tests how it worked. After a few weeks, I have read that IBM had abandoned Kitura project. I was more than happy with my former choice. At the time I started my project current stable version of Vapor was Vapor 3.
And here we are with decision number 2: Vapor as backend API
There was one more decision I should have made at the start of my project but I have made it later. I have no idea why? Maybe because of the easier “entry point” maybe because of toolkit around. This was where to store my data. I was more than sure that I cannot go with SQLite and I went with MySQL and more detailed with its fork MariaDB. Later on, as I get known more with Vapor, how it works and behaves after some recommendations from Vapor’s Discord server (sorry I do not remember who first recommended this to me) I changed my backend storage to PostgreSQL on 10th of February ‘20.
We have now 3rd decision about the foundations of my project. There will be one more but more about it later on.
How would I call it?
Having a good name for every product is important. The goal was to make it unique, recognizable and easy to remember or spell. I tried mixing words: talks, speeches, videos, developer, development and similar. I used their shorter versions but I wasn’t happy with any result I got.
Then came an idea. What are talks all about? They are about coding, they are given on conferences. And I got draft “coding conferences”. It was too long but into the point with the purpose of my app. The path from this point to the final name of “Code Conf” was short. I like it very much. It’s short, it’s catchy. And from the 12th of December ‘19, my new project had its name.
Road to deliver it
After creating initial drafts of UI I have focused on coding backend, designing routes and their purpose, communication with the database. I was using at first Vapor’s Fluent ORM for communication with the database. Unfortunately, I found very fast that it is limiting me while I was creating more complex queries with dependencies between tables. On Vapor’s Discord, I got suggestion by iMike to try his SwifQL package which is DSL for SQL queries written in Swift. Having a background in working with SQL from the times I was coding in PHP I decided to give it a try. And this was a good decision. I could then prepare responses based on multiple objects without unneeded nesting of dependencies.
From the start of my project I was following one man in the Swift community and learned from his brand new blog posts a lot. He was kind so many times to share his free time helping me resolve many of my development struggles. That man is one and only Donny Wals. I was looking for a way to communicate my iOS app with my backend and how to make this code reusable. My own written solution was working but had a huge amount of duplicated code in it. After I read his robust networking post and with his advice, I have rewritten it and made this code clean and reusable. Adding new endpoints to my API was a pleasure from this point.
Time was going, lines of code were growing until the point when I had to implement authorization of my app in backend API. As I decided that I will go with an anonymous account as the base type of account I had to implement keeping user authenticated and at the same time my backend secure. I was testing the implementation of token refreshing with completion handler based networking layer. This made my code complicated and hard to maintain with multiple levels of nested completion handlers. And here we go again … Donny had come to the rescue, yes that Donny had completed this series of blog posts about Combine - and in particular one on refactoring his previous networking implementation to Combine and his Practical Combine book. Having this knowledge I tried to recreate my implementation of the networking layer in Playgrounds using Combine. It wasn’t easy and straightforward to make it work because reactive programming was new to me. But it started to work and as a positive side effect, I got my authentication working in a way I wanted it to work.
From the start, I knew that my app will be free to download and use but it will contain premium features which will require purchasing. I have read case studies on Twitter where people were implementing their IAP’s lightning fast. The solution was Revenue Cat. Having no experience with managing IAP’s I managed to implement them in my app in one evening on the 21st of April ‘20. And their generous pricing model where you only pay when you break a limit of 10k USD of tracked subscriptions per month or upgrade to a more advanced plan.
One week later I had first videos playing in-app, my API was deployed to Heroku and I could prepare first beta tests. But more on this later on.
Few days before I was implementing my IAP’s someone from my iOS Dev list on Twitter had forwarded Dominic Williams` proposal to create iOS developer Mastermind Group. I got interested in this and decided to contact him to get more information about how this would look like. In about a week Dominic had completed our group with Ben Noland and we started to our first meetings. We are from different countries, from different timezones but we agreed to meet every other week. I would not tell you any details about what we are talking on them but joining this group had given me the power to keep going with my project. I felt obliged to show progress on every meeting, prepare new topics to discuss. Those discussions helped me in making decisions about topics I wasn’t sure which way to go. It is also a great way to share knowledge and experience. We all work on our products but we have one certain goal: To make our apps great, every in its own unique way. And I can say now that they had their impact on how my app looks now.
Back to coding
I have invited to my early bird TestFlight some of the very well known guys in our Swift community. I was pleased with their great opinions, little suggestions for improvements and general interest in my app. This made me feel that my app can to be useful to other developers. On my early bird list besides of Donny, Dominic and Ben I got also Dave Verwer, Benedikt Tertechte, Shihab, Pedro Carrasco, Vadim Bulavin, Mateusz Matrejek, Niv Ben-Porath, Christian Weinberger, Tibor Bodecks. Later on, I have invited to beta tests a few more people. Thank you all for taking time testing and reviewing my app on this early stage and later with every new build.
As the first tests were going on a side with the use of API on Heroku it was time for breaking my API at all for almost a week. I was postponing this move for a very long time. But lack of some features in SwifQL for Vapor 3 forced me to do it. I cannot say it was easy. It wasn’t at all. I started a migration to Vapor 4 and Bridges + SwifQL on 2nd of May ‘20. The migration took 7 days and resulted in more than 4000 lines of code changed. If I knew that this migration will require such amount of work beforehand I do not know if I would decide to do it. But better later than never on the 8th of May I got my API fully working with a new setup. I have learned a lot during this process. And where I struggled with errors iMike was so kind to help me on his Discord server. What’s more important every feature in SwifQL and Bridges which was missing to my use case he implemented it almost on the fly. If you ever will be working with Server Side Swift consider using his packages. They are worth trying.
Like almost every iOS app mine also is using User Defaults to store some settings. Isn’t this great to have them synced between your devices where one setting is propagated to all other you have my app installed? Inspired by Paul Hudson’s Unwrap app source code I tried to use Zephyr for syncing some of them. This required some additional work on Apple Developer portal but in one day I got them working. Working with User Defaults “the old way” wasn’t so great experience before Apple had announced Property Wrappers. And you know what? I learned next thing new to me by following Antoine van der Lee. He wrote a simple yet powerful solution for managing User Defaults with Property Wrappers. Adding to this combo my Enum with all UserDefaults names I got very solid and easy to extend code.
Near the end of May, I started getting disappointed with Heroku performance (mostly long pings and slow reaction to requests). I was looking sometime earlier for Vapor hosting solutions. I found Digital Ocean. What’s more Vapor docs are also covering deployment on Digital Ocean. 14 years of managing Linux servers was very helpful this time. I have deployed my API to Digital Ocean in one afternoon on the 31st of May.
I could end this part of my journey saying only: “More code … more crashes”
But I will not leave you without this part of the story. Many devs who I found during working on SwiftUI were reporting problems and lack of its stability. I probably was happy enough to not get them for about 6 months. Of course, I got hit by minor bugs or problems but my app wasn’t crashing almost at all before June. Every week I was closer to the planned launch date, which I decided to be 18th of June. But having lots of crashes was worrying me. It was time to implement some crash reporting tool in my app. But which one to choose. I knew I would not use Crashlitics. During one of many chats with Donny, he said that they are using Sentry in his daily job. Sentry has some free plan so I decided to give it a try. And crash reports started appearing. Some of them were useful and helping me resolve a few issues. As it turned out many of them were not directly connected to my code. That crashes appeared in SwiftUI code, its renderer and memory management. This is what I was unable to fix. I decided to fix other things and wait for WWDC and fixes in the new version of SwiftUI.
The other thing I was trying to resolve was reusing the same view in two different places. In one place everything worked without issues, but on another View was losing its data, despite the fact that this data was supplied to it in both the same way. This led me to Vadim Bulavin’s post about implementing MVVM in SwiftUI. His example is covering whole MVVM architecture, but I didn’t have at that moment too much time to refactor whole logic to MVVM. I decided to try and use some basic concepts from his example. This fixed my issues with empty views instead of filled with data. I was getting disappointed with SwiftUI because of these tricks required. But there was no turning back.
Let’s launch it
On the 30th of May, I have posted my first mysterious tweet with my plan for launching. I wanted to gain some interest in my project without revealing any details then. It had created a huge interest in my project which made me more motivated to work harder heading launch date.
On the 14th of June, I have uploaded the first build for Apple review.
The first one was obvious. My error. When you are attaching IAP’s to App for the first time you have to define them in App’s general info tab. I didn’t know this. Easy fix, few clicks and done.
The second one made me hugely upset. They rejected my business model for the app saying that the features I have implemented are not sufficient to use auto-renewable subscriptions. They wanted me to downgrade to nonrenewable subscriptions. I talked with few people what they think about this and they didn’t also understand this statement. I asked for more explanations in the resolution centre. I waited a few hours for their answer and made one more mistake. I have uploaded second binary with minor fixes and pressed “Submit for Review” button. Just a second after this I realised that I was thrown away from getting an answer to my questions. I had to wait for the second review. That hours were so long … and the result was the same. Rejected
What violations now? Well, the first one was resolved, but with the second one, they said the same. I cannot agree with them and thought I will give myself one more chance to have it accepted my way.
My plan for launching on the 18th of June felt away. With WWDC 20 in a few days, I decided to work on it for a few weeks more and postpone the launch until half of July.
I had one feature on my Todo list which I planned for a future version of Code Conf app. But after some consideration, I felt this could be the game-changer to Apple Review. It was dynamic, it was using one of fast-evolving Apple framework. This feature was “Recommendations”. I used for them Machine Learning and MLRecommender model from Core ML framework. I have prepared a rating matrix for more advanced and precise recommendations. This required also updates to API so I was able to retrieve training data for my model. With a dummy test data, the training worked without issues. But when I tried to use real data generated by my Testflight testers it started to break. I was looking for a solution almost everywhere. I even was considering dropping support for training with ratings and go the easier way. Hopefully, just after WWDC, I posted a tweet which interested one of Apple Engineers in CoreML team. I filed Feedback, posted on Developer Forums. And found the solution on StackOverflow. If you want to read more on this issue I wrote a post on it with more details.
While I resolved training issues I wanted to update model remotely so Code Conf users are well-trained model between new releases of my app. It was possible because Apple allows you to recompile downloaded model on the device and use this recompiled version same way as I was embedded in the binary. Everything worked fine.
In between, I have implemented also Image caching for better user experience based on awesome post by Vadim Bulavin. Having my gem implemented and resolved a few issues found in the meantime, it was time for last chance with Apple Review.
Let’s launch it one more time
I was ready for review. But I was scared with its probable result. I was worried if this time I will get rejection one more time?
On Friday evening I got one more chat with Donny (I swear this is the last time I quote him here ;) ) about my struggles. He replied to me “uh oh :o I’d say go for it!” and assured that I will get easy weekend as Apple Reviewers do not work on weekends.
And you know what? On Saturday evening (CEST timezone) the status of my app changed to “In Review”. How I was stressed then. Fortunately, I went to sleep and slept until 4 AM when I got notification from App Store Connect App. “You got a new message in resolution centre”. I woke up in a second. Whaaat? Why? Not again …
When I logged to read it I saw “Metadata Rejected”. And I calmed down. I knew that my binary with auto-renewable subscriptions is accepted. The only thing was missing 3 lines about those subscriptions in my app description. I have updated it and replied for reviewing metadata again. In about half an hour I got my app APPROVED FOR SALE and “Pending developer action” on Sunday morning. How happy I was with it.
Code Conf content
In between coding I was contacting conference organizers for permission to use their talks in my app. I was pleased by such positive responses. As a result Code Conf app has a right to stream all content it contains without any legal issues. All talks are available to browse and watch for free.
This project took me 9 months to write. This is not the end of it but it just starts. I have many features yet to implement which will make it even better for you. I am working with next conference organisers to add their talks to Code Conf app. If you are a conference organiser and you would like to add your talks to my app please contact me on Twitter. DM’s are open. I want this app to be as much inclusive as possible. That’s why I would love to include talks given not only in English but also in other languages.
At this point, I wanted to say thank you to my beloved Wife for her patience while I was coding a lot. All my beta testers: Donny Wals, Dominic Williams, Ben Noland, Vadim Bulavin, Tibor Bodecs, Mateusz Matrejek, Artur Rymarz, Benedikt Tertechte, Niv Ben-Porath, Christian Weinberger, iMike for all their reports, suggestions and time.
My developer journey and success of this project probably won’t be possible without having access to great resources from the Swift community so additional thanks go to: Antoine van der Lee, John Sundell, Paul Hudson, Mark Moeykens, Majid Jabrayilov and many more I have learned from during this time.
Code Conf app is available in App Store all around the world from today - 14th of July 2020.
Sometimes you have to stay calm, step back and don't give up and push forward your dreams. My dream has become a reality today.
- | Paweł Madej
Licensing: Content Code Apache 2.0