My experience building an iOS App with the help of Chat GPT-4 (#2)

How I connected with backend and saved data in DB

Andrzej Ryl
8 min readJun 5, 2023

Intro for newcomers

I’m an Android developer, never had any experience with iOS and yet I’m building a full iOS version of my own Android app called Uncover with the help of Chat GPT.

Here is the link to intro of this series — https://medium.com/@andrzej.ryl/my-experience-building-an-ios-app-with-the-help-of-chat-gpt-4-intro-dc5e402a6ea8

This story will focus around backend connection and database handling. I started off with a goal of hitting my backend, saving data in DB and then using it to do a starting navigation based on this data.

Last time I achieved a working SplashScreen that after 2 seconds navigates to the onboarding screen. Seems legit but has nothing to do with the actual logic I had in my Android app so it was time to do some real coding.

Idea was simple — we show a SplashScreen, hit my backend endpoint, download some initial data (including the information specifying whether the backend is up or down for maintenance), saving this in DB and then navigating to one of 7 screens/popups depending on user state:
- Backend maintenance screen if BE is under maintenance
- App update required popup if the version of the app is not supported
- Onboarding screen if the user is in the app for the first time
- Login screen if the user is not logged in
- Terms of use popup if the user is logged in anonymously and he didn’t accept the current version of Term of Use
- error screen if anything goes wrong
- Main screen otherwise

And I thought I had a simple app 😂

Networking

I started with simple message to Chat to figure out how can I connect to my backend and retrieve some data. This time I prepared myself and tweaked my prompt to skip my previous problems right away.

Hi. Can you help me do a network call to my backend in Swift? 
I am an experienced Android Developer so you can describe it to me
in reference to Retrofit library from Android.
I'm building iOS app in Swift and Swift UI targeting iOS 15.
If you don't know something, please ask or simply tell me. Don't lie

His response was very informative. He made a clear comparison between Retrofit and Swift’s URLSession, explained to me how I can serialize/deserialize objects to JSON (it’s called encoding and decoding in iOS world) and how I can connect everything to get my response.

I also asked him to implement this code into my VIPER architecture and he prepared everything for me. I even was able to get information out of him how to prepare my models so they are readable in Swift and work with my naming from backend. Here is what I got

// Models
struct InitialDataReponse: Codable {
let isBeAvailable: Bool
let minAndroidAppVersion: Int
let currentTermsOfUseVersion: Int
let appVersions: [AppVersionResponse]

enum CodingKeys: String, CodingKey {
case isBeAvailable = "is_be_available"
case minAndroidAppVersion = "min_android_app_version"
case currentTermsOfUseVersion = "current_terms_of_use_version"
case appVersions = "app_versions"
}
}

struct AppVersionResponse: Codable {
let id: String
let name: String
let versionCode: Int
let priority: Int
let description: String

enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case versionCode = "version_code"
case priority = "priority"
case description = "description"
}
}


// UncoverApi (something like Retrofit interface for all endpoints)
import Foundation

enum UncoverApi {
case initialData

var path: String {
switch self {
case .initialData:
return PATH_TO_ENDPOINT
}
}

var url: URL {
let backendURL = Config.shared.backendURL!
let urlString = backendURL + self.path
return URL(string: urlString)!
}
}


// UncoverServiceApi (something like built Retrofit instance)

import Foundation

class UncoverApiService {

func getInitialData(
completion: @escaping (Result<InitialDataReponse, Error>) -> Void
) {
fetchResponse(
from: UncoverApi.initialData.url.absoluteString,
completion: completion
)
}
}

He was even nice enough to help me easily spot my typo. I mistyped one of the fields in my AppVersionResponse model and XCode was giving me a not-very-helpful error “Type ‘AppVersionResponse’ does not conform to protocol ‘Decodable’”. I just pasted my model to Chat, gave him this error, and he instantly told me that I just had a typo in one of my fields 🎉 I think that integrating Chat to XCode will be a game changer for our iOS friends 😆 Android Studio is much better at that.

Wiring all together was super easy this time. I just told Chat what I need and he just blurted it out without any hiccups

I want to have a repository between interactor and apiservice. 
This repository should call ApiService method, map model to domain one
and then return it through callback to interactor

This is what I got

protocol Repository {
func getAppVersion(
completion: @escaping (Result<AppVersion, Error>) -> Void
)
}

class AppVersionRepository: Repository {
private let apiService: APIService

init(apiService: APIService) {
self.apiService = apiService
}

func getAppVersion(completion: @escaping (Result<AppVersion, Error>) -> Void) {
apiService.fetch(from: .appVersion) { (result: Result<AppVersionResponse, Error>) in
switch result {
case .success(let response):
let appVersion = self.mapToDomainModel(response: response)
completion(.success(appVersion))
case .failure(let error):
completion(.failure(error))
}
}
}

private func mapToDomainModel(response: AppVersionResponse) -> AppVersion {
return AppVersion(id: response.id, name: response.name, versionCode: response.versionCode, priority: response.priority, description: response.description)
}
}

As you can see Chat just basically prepared a logic for grabbing appVersion but changing it to actually do what I need was simple enough. I divided it between layers and modules I had in code and that was it! I could just basically test it. Couple of seconds later I saw this in my terminal

Received JSON: 
{
"id":1,
"app_versions":[],
"is_be_available":true,
"min_android_app_version":0,
"current_terms_of_use_version":2
}

And voila! My app just opened SplashScreen, made a call to backend and based on received data made a decision to navigate to Main screen.

One important thing to notice here is that during this process Chat has made a mistake again. He initially provided me with incorrect code to build absolute URL with 100% certainty

var url: URL {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "yourapi.com"
urlComponents.path = self.path

return urlComponents.url!
}

But I have to admit that I just pasted the problem I saw and he immediately knew what was wrong and corrected himself

// Looking back at the code for the API enum, it seems I made an error. 
// The url computed property is incorrectly defined.
// It creates a new URL instance each time it's called, rather
// than appending to a base URL.

// Here's how you can modify the API enum to correctly append the path
// to your base URL:

var url: URL {
let backendURL = Config.shared.backendURL!
let urlString = backendURL + self.path
return URL(string: urlString)!
}

It worked but again — he mislead me at first so his urgence to be helpful is still much bigger than being right which can be very irritating.

Saving data

After I finished with this simple exercise I wanted to deep deeper. Actually save the data that I receive from network, mix it with local data (like app open counter) and create more advanced logic in my code.

I opened a separate chat with my AI friend (as I already learned that conversations that are too big start to confuse him) and simply asked.

Hi. Can you help me with saving and retrieving data in Swift 
from local storage? I am an experienced Android Developer so you
can describe it to me in reference to SharedPreferences library from Android.
I'm building iOS app in Swift and Swift UI targeting iOS 15.
If you don't know something, please ask or simply tell me.
Don't lie or try to guess. I want you to only show the code that you
are certain is going to work

He did a full explanation of UserDefaults which is exactly what I need — a simple test key-value storage. He did however warn me that this method is only for storing non-sensitive data because it can be easily read by an attacker. This was a nice addition.

Regarding my last ask however, he basically “lawyered up” 🤣 He instantly added this special note protecting him in case of “mistakes”

This information is up-to-date and relevant to your target of iOS 15 
as of my training cut-off in September 2021. If Apple has introduced
new APIs or libraries for managing local storage after that time,
I would recommend checking the latest documentation or resources online.

I guess I cannot really expect him to be 100% certain before posting any code because you can never be 100% certain — even when you’re human.

After that simple explanation however I was able to easily setup entire “framework” for saving/retrieving data from local storage and even setup a separate storages for different purposes. It was an easy job but Chat did help with couple of minor troubles I had. I have to admit that writing this part of code was a total breeze.

Last interesting thing I achieved with Chat was to parse iOS app version. In Android we have versionName and versionCode but in iOS it looks like we only have versionName which is a string. I do however have a logic based on the version number so I asked Chat to parse it for me. He understood immediately what I meant which I think was kinda cool.

// ME
let's say the version is "1.2.0" - can I parse this to retrieve
major, minor and patch numbers as ints?

// CHAT
Yes, you can parse a version string to retrieve the major,
minor, and patch numbers as Int. Here's how you can do it in Swift:

let version = "1.2.0"
let versionComponents = version.split(separator: ".").compactMap { Int($0) }

if versionComponents.count == 3 {
let major = versionComponents[0]
let minor = versionComponents[1]
let patch = versionComponents[2]

print("Major: \(major), Minor: \(minor), Patch: \(patch)")
}

Couple of minutes later I had pretty much everything I needed

  • app starts with SplashScreen
  • it makes a call to backend
  • saves needed data in local storage
  • increases app open counter
  • checks for mix of initial data retrieved from server and locally kept data (version number and app open count)
  • makes a decision on which screen to show

I skipped info about login because auth is not setup yet but I’ll get to that.

Summary of #2

I have to say that this time the work was much smoother. I’m not sure if this was because the fact that I divided my chats (one for network, one for local data) instead of doing everything in one chat or just because the task was much simpler. Nonetheless this time I did make some cool progress in a really short time. I have to say that I’m starting to have hope now 😄

We’ll see how long 😅

Next part is UI only… I’ll try to build full onboarding screen with some easy swiping. We’ll see how that goes.

--

--

Andrzej Ryl

I've been an Android developer since 2014. Currently I'm an Android team lead in my day job and working on my own personal project after hours. Huge dog lover