storing temporary data from user through different controllers before saving them - swift

Best practice for storing temporary data during session? I have an app that allows me to register a customer in a medical service, inserting biometrical and personal data (name, weight, personal measures etch.) once the data are saved, you go to other pages, where you can perform some operation using the data. Only at the very end of this path (after 3-4 controllers), I save the Customer in local database via Realm.
during register phase, I need to store this "TempUser"'s data, with can be accessed from multiple controllers.
I think I could use singletons, but not sure IF and HOW
I fund this but not sure which solution fits better.
my idea:
class AppSession {
static var shared = AppSession()
private init() {}
var currentPatient: Patient?
}
class Patient {
let shared = Patient()
private init() {}
var name: String? = ""
var weight: Double = 0.0
}
and in my textfield delegate:
AppSession.shared.currentPatient?.name = data //text from textfield
otherwise, using Patient as a struct, not a class

It is hard to tell what solution fits for every individual case. It really depends how broadly the data is used in your app. Is it common to every part or just some specific ViewControllers ? How many times it is retrieved and updated ? Also it depends on the architecture pattern you are using and many more factors. So in short, your solution using a singleton pattern may be valid, but can't tell for sure without having a deep look at your project. ​If you decide to stick with your solution, you can omit the AppSession class and directly access your Patient.
class Patient {
static let shared = Patient()
var name: String? = ""
var weight: Double = 0.0
}
And update it:
Patient.shared.name = ""
Even though your solution using a singleton pattern may be valid as mentioned above, you should also consider the user point of view: Do you want the user to be able to continue the flow even after the app was killed ? If yes, than you should save the data after every step(So maybe to KeyChain as what you describe is kind of sensitive data). Or a better solution is to store the data on the server side, which gives the user the flexibility to continue even on an other device(maybe android).
But you can say, that you really do not care about KeyChain neither communicating with a server, and want to do it locally. In this case personally I would pass the data between the controllers, and avoid Singletons. Let's take your Patient object and create a protocol which will require every UIViewController that conforms to it to have a stored property of Patient.
struct Patient {
var name: String? = ""
var weight: Double = 0.0
}
protocol MyOnboardingProtocol: class {
var data: Patient { get set }
}
With the MyOnboardingProtocol you will mark every controller which will require to hold the Patient data. In this way you will force the ViewControllers to hold the Patient object, and will know for the first glance if your ViewController belongs to the "Onboarding" flow.
In your first UIViewController, where your flow begins, you will initialise your Patient object(maybe also make some update on it from user input), and pass along for the next UIViewController:
class MyFirstViewController: UIViewController, MyOnboardingProtocol {
var data: Patient = Patient()
func nextViewController() {
let viewController = MySecondViewController(data: data)
navigationController?.pushViewController(viewController, animated: true)
}
}
For every other UIViewController down the road you will pass the updated object into the init:
class MySecondViewController: UIViewController, MyOnboardingProtocol {
var data: Patient
init(data: Patient) {
self.data = data
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
But as I mentioned before, what I described is just one way of doing it. It will really depend on your individual case and business logic. As I want to keep my answer short, I encourage you to discover the topic a bit more, there has been many great articles about this topic.

Related

SwiftUI with complex MVVM (Repository + Nested ObservedObject)

Explanation
I am still in the process of learning to utilize SwiftUI patterns in the most optimal way. But most SwiftUI MVVM implementation examples I find are very simplistic. They usually have one database class and then 1-2 viewmodels that take data from there and then you have views.
In my app, I have a SQLite DB, Firebase and different areas of content. So I have a few separate model-vm-view paths. In the Android equivalent of my app, I used a pattern like this:
View - ViewModel - Repository - Database
This way I can separate DB logic like all SQL queries in the repository classes and have the VM handle only view related logic. So the whole thing looks something like this:
In Android this works fine, because I just pass through the LiveData object to the view. But when trying this pattern in SwiftUI, I kind of hit a wall:
It doesn't work / I don't know how to correctly connect the Published objects of both
The idea of "chaining" or nesting ObservableObjects seems to be frowned upon:
This article about Nested Observable Objects in SwiftUI:
I’ve seen this pattern described as “nested observable objects”, and it’s a subtle quirk of SwiftUI and how the Combine ObservableObject protocol works that can be surprising. You can work around this, and get your view updating with some tweaks to the top level object, but I’m not sure that I’d suggest this as a good practice. When you hit this pattern, it’s a good time to step back and look at the bigger picture.
So it seems like one is being pushed towards using the simpler pattern of:
View - ViewModel - Database Repository
Without the repository in-between. But this seems annoying to me, it would make my viewmodel classes bloated and would mix UI/business code with SQL queries.
My Code
So this is a simplified version of my code to demonstrate the problem:
Repository:
class SA_Repository: ObservableObject {
#Published var selfAffirmations: [SelfAffirmation]?
private var dbQueue: DatabaseQueue?
init() {
do {
dbQueue = Database.sharedInstance.dbQueue
fetchSelfAffirmations()
// Etc. other SQL code
} catch {
print(error.localizedDescription)
}
}
private func fetchSelfAffirmations() {
let saObservation = ValueObservation.tracking { db in
try SelfAffirmation.fetchAll(db)
}
if let unwrappedDbQueue = dbQueue {
let _ = saObservation.start(
in: unwrappedDbQueue,
scheduling: .immediate,
onError: {error in print(error.localizedDescription)},
onChange: {selfAffirmations in
print("change in SA table noticed")
self.selfAffirmations = selfAffirmations
})
}
}
public func updateSA() {...}
public func insertSA() {...}
// Etc.
}
ViewModel:
class SA_ViewModel: ObservableObject {
#ObservedObject private var saRepository = SA_Repository()
#Published var selfAffirmations: [SelfAffirmation] = []
init() {
selfAffirmations = saRepository.selfAffirmations ?? []
}
public func updateSA() {...}
public func insertSA() {...}
// + all the Firebase stuff later on
}
View:
struct SA_View: View {
#ObservedObject var saViewModel = SA_ViewModel()
var body: some View {
NavigationView {
List(saViewModel.selfAffirmations, id: \.id) { selfAffirmation in
SA_ListitemView(content: selfAffirmation.content,
editedValueCallback: { newString in
saViewModel.updateSA(id: selfAffirmation.id, newContent: newString)
})
}
}
}
}
Attempts
Obviously the way I did it here is wrong, because it clones the data from repo to vm once with selfAffirmations = saRepository.selfAffirmations ?? [] but then it never updates when I edit the entries from the view, only on app restart.
I tried $selfAffirmations = saRepository.$selfAffirmations to just transfer the binding. But the repo one is an optional, so I'd need to make the vm selfAffirmations an optional too, which would then mean handling unnecessary logic in the view code. And not sure if it would even work at all.
I tried to do it manually with Combine but this way seemed to not be recommended and fragile. Plus it also didn't work:
selfAffirmations = saRepository.selfAffirmations ?? []
cancellable = saRepository.$selfAffirmations.sink(
receiveValue: { [weak self] repoSelfAffirmations in
self?.selfAffirmations = repoSelfAffirmations ?? []
}
)
Question
Overall I would just need some way to pass through the data from the repo to the view, but have the vm be in the middle as a separator. I read about the PassthroughSubject in Combine, which sounds like it would be fitting, but I'm not sure if I am just misunderstanding some concepts here.
Now I am not sure if my architecture concepts are wrong/unfitting, or if I just don't understand enough about Combine publishers yet to make this work.
Any advice would be appreciated.
After getting some input from the comments, I figured out a clean way.
The problem for me was understanding how to make a property of a class publish its values. Because the comments suggested that property wrappers like #ObservedObject was a frontend/SwiftUI only thing, making me assume that everything related was limited to that too, like #Published.
So I was looking for something like selfAffirmations.makePublisher {...}, something that would make my property a subscribable value emitter. I found that arrays naturally come with a .publisher property, but this one seems to only emit the values once and never again.
Eventually I figured out that #Published can be used without #ObservableObject and still work properly! It turns any property into a published property.
So now my setup looks like this:
Repository (using GRDB.swift btw):
class SA_Repository {
private var dbQueue: DatabaseQueue?
#Published var selfAffirmations: [SelfAffirmation]?
// Set of cancellables so they live as long as needed and get deinitialiazed with the class end
var subscriptions = Array<DatabaseCancellable>()
init() {
dbQueue = Database.sharedInstance.dbQueue
fetchSelfAffirmations()
}
private func fetchSelfAffirmations() {
// DB code....
}
}
And viewmodel:
class SA_ViewModel: ObservableObject {
private var saRepository = SA_Repository()
#Published var selfAffirmations: [SelfAffirmation] = []
// Set of cancellables to keep them running
var subscriptions = Set<AnyCancellable>()
init() {
saRepository.$selfAffirmations
.sink{ [weak self] repoSelfAffirmations in
self?.selfAffirmations = repoSelfAffirmations ?? []
}
.store(in: &subscriptions)
}
}

How to manage Realm Notification Tokens centrally

I have a number of notification tokens scattered around my app's classes. I would like to create a class, e.g. a RealmNotificationTokensManager, to keep track of instantiated tokens to be able to know what tokens are active at a specific point of time or view in the app to make sure to invalidate them timely etc.
In total, there are around 35 notification tokens in my app.
Besides having all these tokens as properties of the manager class, I'd like to be able to group them in an array to be able to run queries on it. But if I maintain that array manually, e.g. if I add a new token, I need to make sure I also add it to the array of all tokens. This is a bit error prone since one can forget to add the new token to the array.
As far as I understand there might be away to make it safer by using an enum and maybe CaseIterable protocol but I cannot find a way to do it.
Would anybody give some direction how to achieve it?
If I understand the question correctly, it should be fairly straight forward to create a 'centralized' notification manager. Keep mind this is very bare bones for brevity but it's how we do it.
Suppose we have a PersonClass and DogClass and we want to add observers to those types of objects. We will have a class var that contains the people and dog results as well as a var representing the manager
class ViewController: NSViewController {
var peopleResults: Results<PersonClass>? = nil
var dogResults: Results<DogClass>? = nil
let manager = NotificationManager()
and then somewhere along the way we'll load in the people and dogs into results and add an observer
func observePeople() {
var token = NotificationToken()
token = self.peopleResults!.observe { changes in
switch changes {
...
}
}
self.manager.notificationArray.append(token)
}
and then dogs
func observeDogs() {
var token = NotificationToken()
token = self.dogResults!.observe { changes in
switch changes {
...
}
}
self.manager.notificationArray.append(token)
}
and the notification manager would look like this
class NotificationManager {
var notificationArray = [NotificationToken]()
}
now this next part is use case dependent
if I add a new token, I need to make sure I also add it to the array
of all tokens. This is a bit error prone since one can forget to add
the new token to the array.
I get the gist of that statement so if you know the exact notifications you will be using, those could be implemented into the manager itself. So install of creating tokens everywhere in the app, do it all within the manager
For example
class NotificationManager {
var notificationArray = [NotificationToken]()
func observeDogs() {
let dogResults = realm.objects(DogClass.self)
var token = NotificationToken()
token = dogResults.observe { changes in
switch changes {
//..
}
}
self.notificationArray.append(token)
}
}
then when you want to observe Dogs, one-line will do it
func observeDogs() {
self.manager.observeDogs()
}
If you want to 'lock it down' then using enums is a great idea
enum Observers {
case people
case dogs
}
class NotificationManager {
var notificationArray = [NotificationToken]()
func addObserverTo(whatToObserve: Observers) {
switch whatToObserve {
case .people:
//add people observer
case .dogs:
//add dogs observer
default:
break
}
}
}
and then called like this
self.manager.addObserverTo(whatToObserve: .people)

Where's the best place to call methods that interact with my database?

I'm creating an app that interacts with a Firestore database. As of now I have a singleton class, DatabaseManager that has all the methods relating to the Firestore database (i.e. get/post methods).
I have a User model called User that has properties such as name, email, photoURL, and some app-specific properties. Any user can edit their profile to update information from a view controller called EditProfileViewController.
Now my question is: is it best to call the DatabaseManager.shared.updateInfo(forUser: user) (where user is a User instance) from EditProfileViewController, User, or some other place?
Sorry if this is an obvious question, but there's going to be a lot of points in the app where I'll need similar logic so I wanted to know what's the best design. Also I'm sure this question has more to with MVC than it does Firebase/Swift.
A couple of thoughts:
Rather than accessing the singleton directly with, DatabaseManager.shared.update(for:), I might instead have a property for the database manager, initialize/inject it with the DatabaseManager.shared, and have whatever needs to interact with the database use that reference, e.g., dataManager.update(for:). The goal would be to allow your unit tests to mock a database manager if and when necessary.
I would not be inclined to have a view controller interact directly with the DatabaseManager. Many of us consider the view controller, which interacts directly with UIKit/AppKit objects, as part of the broader “V” of MVC/MVP/MVVM/whatever. We’d often extricate business logic (including interaction with the database manager) out of the view controller.
I personally wouldn’t bury it under the User object, either. I’d put it in an extension of the database manager, and called from the view model, the presenter, or whatever you personally want to call that object with the business logic.
Is there a reason you're using a singleton to contain all the Firestore logic? User model should contain the method updateInfo.
Here's an example i've used with Firestore:
class Group {
// can read the var anywhere, but an only set value in this class
private(set) var groupName: String!
private(set) var guestsInGroup: Int!
private(set) var joinedGroup: Bool!
private(set) var timeStampGroupCreated: Date!
private(set) var documentId: String!
init(groupName: String, guestsInGroup: Int, joinedGroup: Bool, timeStampGroupCreated: Date, documentId: String) {
self.groupName = groupName
self.guestsInGroup = guestsInGroup
self.joinedGroup = joinedGroup
self.timeStampGroupCreated = timeStampGroupCreated
self.documentId = documentId
}
// method to parse Firestore data to array, that table view will display
class func parseData(snapshot: QuerySnapshot?) -> [Group]{
var groups = [Group]()
guard let snap = snapshot else { return groups }
for document in snap.documents {
let data = document.data()
let groupName = data[GROUP_NAME] as? String ?? "No Group Name"
let guestsInGroup = data[GUESTS_IN_GROUP] as? Int ?? 0
let joinedGroup = data[JOINED_GROUP] as? Bool ?? false
let timeStampGroupCreated = data[TIMESTAMP_GROUP_CREATED] as? Date ?? Date()
let documentId = document.documentID
// add objects with fetched data into thoughts array
let newGroup = Group(groupName: groupName, guestsInGroup: guestsInGroup, joinedGroup: joinedGroup, timeStampGroupCreated: timeStampGroupCreated, documentId: documentId)
groups.append(newGroup)
}
return groups
}
}

Patterns: Singletons vs. Static vars and methods approach

I am reading a lot about the Singleton Pattern. I am currently using it to store groups of global state in my first app. I am reaching a point where I wonder which approach to implement API client classes and similar with.
Are Structs with static vars and static functions having the same issues?
To illustrate what I mean, I've tried to write the same heavily simplified and exact same(?) scenario twice.
1. A singleton being worked with by a view controller:
struct APIClientSingletonClass {
static let shared = APIClientSingletonClass()
var stateOfSomehting: Bool = true
var stateOfSomehtingMore: Bool = false
var stateNumber: CGFloat = 1234
var stateOfSomehtingComputed: CGFloat {
return stateNumber * 10
}
func convertSomethingToSomethingElse() {
// calling method in self like this:
otherMethod()
}
func otherMethod() {
// doing stuff here
}
}
// Calling APIClient from outside:
class ViewControllerTalkingToSingleton: UIViewController {
var api = APIClientSingletonClass.shared
override func viewDidLoad() {
super.viewDidLoad()
api.convertSomethingToSomethingElse()
api.stateOfSomehting = false
}
}
2. Another approach:
struct APIClientStruct {
static var stateOfSomehting: Bool = true
static var stateOfSomehtingMore: Bool = false
static var stateNumber: CGFloat = 1234
static var stateOfSomehtingComputed: CGFloat {
return stateNumber * 10
}
static func convertSomethingToSomethingElse() {
// calling method in self like this:
APIClientStruct.otherMethod()
}
static func otherMethod() {
// doing stuff here
}
}
// Calling APIClient from outside:
class ViewControllerTalkingToStruct: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
APIClientStruct.convertSomethingToSomethingElse()
APIClientStruct.stateOfSomehting = false
}
}
What do you guys think? Is approach 2 falling into the same traps that seem to make Singletons such a double-edged sword?
Any input is really appreciated!
Best from Berlin
EDIT:
This thread is pretty interesting, but I'm not sure it really relates to my question:
Difference between static class and singleton pattern?
Since there are many perspectives on this topic, let me specify:
Does my approach 2 have the same problem implications with testing and code maintainability?
A class-based singleton is the way to go, provided you accommodate for dependency injection for your tests. The way to do this is to create a single singleton for your app, called, say, DependencyManager. In your AppDelegate (or from other classes if needed), you'd create whatever controllers, network services, realm models, etc you want to hang on your DependencyManager, and then assign them to the DependencyManager. This code would be skipped by your unit tests.
Your unit tests can then access the DependencyManager (and thus instantiate the DependencyManager during first access), and populate it with mock versions of those controllers and services to whatever degree each unit test desires.
Your UIViewControllers, your MVVM view models, etc... can access the DependencyManager as a singleton, and thus get either the real controllers and services, or a mock version of them, depending on if you're running the app or unit tests.
If you're doing MVVM, I also recommend that when a UIViewController is about to create its view model class, that it first checks a special property in the DependencyManager to see if a mockViewModel exists. A single property can serve this purpose, as only one of your UIViewControllers ever would be tested at once. It'd use that property instead of creating a new view model for itself. In this way, you can mock your view models when testing each UIViewController. (There's other tricks involved to being able to prop up a single UIViewController for testing, but I won't cover that here).
Note that all of the above can work very nicely with an app that also wants to use storyboards and/or nibs. People are so down on storyboards because they can't figure out how to do dependency injection of mock services for their view controllers. Well, the above is the solution! Just make sure in your AppDelegate to load the storyboard AFTER setting up the DependencyManager. (Remove the storyboard name from your info.plist, and instantiate it yourself in AppDelegate).
I've written a few shipped apps this way, as well as some sample apps for an SDK, along with the tests. I highly recommend the approach! And be sure to write your unit tests and viewController tests either during or at least immediately after development of each such class, or you'll never get around to them!
What generally makes sinlgetons hard to test is that the singleton objects are typically always accessed directly . Because of this, you don't have a means to substitute the real singleton object (e.g. a data-store that's backed by a database) with a mock object for testing (e.g. a data-store that's backed by an easily-configurable array of predefined test data).
Using static members has the same fundamental issue. When referencing a static member directly, you don't have a means of substituting a mock object in place of the real prod implementation.
The solution to this is quite simple: don't access singleton members directly. What I do is something like this:
// An example of a dependency.
protocol DataAccessLayer {
func getData() -> [Int]
}
// A real implementation of DataAccessLayer, backed by a real production database
class ProdDB: DataAccessLayer {
static let instance = ProdDB()
private init() {}
func getData() -> [Int] {
return [1, 2, 3] // pretend this actually queries a DB
}
}
// A mcok implementation of DataAccessLayer, made for simple testing using mock data, without involving a production database.
class MockDB: DataAccessLayer {
func getData() -> [Int] {
return [1, 2, 3] // The mock *actually* hardcodes this data
}
}
// A protocol that stores all databases and services used throughout your app
protocol ServiceContextProtocol {
var dataAccessLayer: DataAccessLayer { get } // Present via protocol, either real impl or mock can go here
//var fooAPIGateway: FooAPIGateway { get }
//... add all other databases and services here
}
// The real service context, containing real databases and service gateways
class ProdServiceContext: ServiceContextProtocol {
let dataAccessLayer: DataAccessLayer = ProdDB.instance
//var fooAPIGateway: ProdFooAPIGateway { get }
//... add all other prod databases and services here
}
// A mock service context, used in testing, which provides mocked databases and service gatways
class MockServiceContext: ServiceContextProtocol {
let dataAccessLayer: DataAccessLayer = MockDB()
//var fooAPIGateway: MockFooAPIGateway { get }
//... add all other mock databases and services here
}
let debug = false // Set this true when you're running in a test context
// A global variable through which you access all other global state (databases, services, etc.)
let ServiceContext: ServiceContextProtocol = debug ? MockServiceContext() : ProdServiceContext()
// Always reference ServiceContext.dataAccessLayer, ServiceContext.fooAPIGateway, etc.
// and *never* reference ProdDB.instance of MockDB directly.
I would use a Class based Singleton. Just remember the 2 criteria for having a singleton. You want GLOBAL ACCESS and SINGLE INSTANCE in your program. There is a couple problems where struct based singleton would fail. Once you assign a struct to a new variable, Swift makes a complete copy under the hood.
Another useful snip of information can be found using this link.
What's the difference between Struct based and Class based singletons?

How to reduce mutability with nested objects stored in Realm?

Full code on github
I am trying to rewrite my app to reduce mutability and take advantage of functional programming. I am having trouble figuring out where to start, since it seems like my architecture is to use modification in place almost everywhere. I could use some advice on a simple starting point of how to break this down into smaller pieces where I am maintaining immutability at each modification. Should I change my data storage architecture so that I am only storing/modifying/deleting the leaf objects?
Right now, from the root ViewController, I load my one monster object ExerciseProgram (which contains a RealmList of Exercise objects, which contains a RealmList of Workouts, which contains a RealmList of Sets....)
final class ExerciseProgram: Object {
dynamic var name: String = ""
dynamic var startDate = NSDate()
dynamic var userProfile: User?
var program = List<Exercise>()
var count: Int {
return program.count
}
}
Loaded here one time in MasterTableViewController.swift:
func load() -> ExerciseProgram? {
let realm = try! Realm()
return realm.objects(ExerciseProgram).first
}
and then modify the single ExerciseProgram object in place throughout the app, such as when recording a new workout.
To create a new Workout, I instantiate a new Workout object in RecordWorkoutTableViewController.swift:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if doneButton === sender {
if let date = newDate, weight = newWeight, setOne = newSetOne, setTwo = newSetTwo {
let newSets = List<WorkSet>()
newSets.append(WorkSet(weight: weight, repCount: setOne))
newSets.append(WorkSet(weight: weight, repCount: setTwo))
newWorkout = Workout(date: date, sets: newSets)
}
}
}
Which unwinds to ExerciseDetailTableViewController.swift where the storage occurs into the same monster ExerciseProgram object retrieved at the beginning:
#IBAction func unwindToExerciseDetail(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? RecordWorkoutTableViewController, newWorkout = sourceViewController.newWorkout {
let realm = try! Realm()
try! realm.write {
exercise.recordWorkout(newWorkout)
}
}
}
This behavior is replicated all over my app. If I want to edit or delete an existing workout, it's exactly the same.
The Exercise class is just this:
final class Exercise: Object {
dynamic var name = ""
dynamic var notes: String?
var workoutDiary = List<Workout>()
dynamic var goal = 0
...
func recordWorkout(newWorkout: Workout) {
workoutDiary.append(newWorkout)
}
func replaceWorkout(originalWorkout: Workout, newWorkout: Workout) {
workoutDiary[workoutDiary.indexOf(originalWorkout)!] = newWorkout
}
}
From what I can tell, looking at that schema, no, you shouldn't change it. If it's representing the types of information and their relations properly and it's already working in your app, then there's no need to change it.
If you feel it is overly complex or confusing, then it may be necessary to go back and look at your data model design itself before actually doing more work on the code itself. Review each relationship and each property in the linked objects, and make sure that it's absolutely critical that the data is saved at that level. In any case, Realm itself is very good at handling relationships between objects, so it's not 'wrong' to have several layers of nested objects.
Either way, Realm itself lends itself pretty well to functional programming since every property is explicitly immutable out of the box. Functional programming doesn't mean everything has to be immutable always though. Inevitably, you'll have to reach a point where you'll need to save changes to Realm; the mindset behind it is that you're not transforming data as you're working on it, and you minimise the number of points that actually do so.