Skip to content

Commit

Permalink
Store providers
Browse files Browse the repository at this point in the history
Create a common interface to provide persistent data storage,
currently implemented one provider to store data in the
`UserDefaults` for local persistence
  • Loading branch information
YamiDaisuke committed Oct 28, 2020
1 parent 35a78f1 commit 319bc2e
Show file tree
Hide file tree
Showing 10 changed files with 406 additions and 3 deletions.
32 changes: 32 additions & 0 deletions TodoAll.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
81DCC2D4254267CC007A20DF /* TaskListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81DCC2D3254267CC007A20DF /* TaskListView.swift */; };
81DCC2D6254268FE007A20DF /* TaskRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81DCC2D5254268FE007A20DF /* TaskRowView.swift */; };
81DCC2D8254275BF007A20DF /* TaskRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81DCC2D7254275BF007A20DF /* TaskRowViewModel.swift */; };
F8B425452548FEFF00F93641 /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B425442548FEFF00F93641 /* Provider.swift */; };
F8B4254A2549011900F93641 /* LocalProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B425492549011900F93641 /* LocalProvider.swift */; };
F8B42550254904DC00F93641 /* Identifiable+TypeName.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B4254F254904DC00F93641 /* Identifiable+TypeName.swift */; };
F8B42558254910ED00F93641 /* TestLocalProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B42557254910ED00F93641 /* TestLocalProvider.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -65,6 +69,10 @@
81DCC2D3254267CC007A20DF /* TaskListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskListView.swift; sourceTree = "<group>"; };
81DCC2D5254268FE007A20DF /* TaskRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowView.swift; sourceTree = "<group>"; };
81DCC2D7254275BF007A20DF /* TaskRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRowViewModel.swift; sourceTree = "<group>"; };
F8B425442548FEFF00F93641 /* Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = "<group>"; };
F8B425492549011900F93641 /* LocalProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalProvider.swift; sourceTree = "<group>"; };
F8B4254F254904DC00F93641 /* Identifiable+TypeName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identifiable+TypeName.swift"; sourceTree = "<group>"; };
F8B42557254910ED00F93641 /* TestLocalProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestLocalProvider.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -123,6 +131,7 @@
81DCC294254257E3007A20DF /* TodoAll */ = {
isa = PBXGroup;
children = (
F8B4254E254904BD00F93641 /* Utils */,
81B2C262254387C900F18B79 /* UI */,
81DCC2D2254267B8007A20DF /* View */,
81DCC2CF254263FC007A20DF /* ViewModel */,
Expand Down Expand Up @@ -151,6 +160,7 @@
children = (
81DCC2AC254257E8007A20DF /* TodoAllTests.swift */,
81DCC2AE254257E8007A20DF /* Info.plist */,
F8B42557254910ED00F93641 /* TestLocalProvider.swift */,
);
path = TodoAllTests;
sourceTree = "<group>";
Expand All @@ -167,6 +177,7 @@
81DCC2C5254257F5007A20DF /* Model */ = {
isa = PBXGroup;
children = (
F8B425432548FE7400F93641 /* Providers */,
81DCC2CA25426077007A20DF /* Services */,
81DCC2C625425800007A20DF /* Entity */,
);
Expand Down Expand Up @@ -208,6 +219,23 @@
path = View;
sourceTree = "<group>";
};
F8B425432548FE7400F93641 /* Providers */ = {
isa = PBXGroup;
children = (
F8B425442548FEFF00F93641 /* Provider.swift */,
F8B425492549011900F93641 /* LocalProvider.swift */,
);
path = Providers;
sourceTree = "<group>";
};
F8B4254E254904BD00F93641 /* Utils */ = {
isa = PBXGroup;
children = (
F8B4254F254904DC00F93641 /* Identifiable+TypeName.swift */,
);
path = Utils;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand Down Expand Up @@ -340,23 +368,27 @@
buildActionMask = 2147483647;
files = (
81DCC2D12542640E007A20DF /* TaskListViewModel.swift in Sources */,
F8B42550254904DC00F93641 /* Identifiable+TypeName.swift in Sources */,
81DCC2D6254268FE007A20DF /* TaskRowView.swift in Sources */,
81DCC2CC25426087007A20DF /* TaskService.swift in Sources */,
81DCC2C825425F60007A20DF /* Task.swift in Sources */,
F8B425452548FEFF00F93641 /* Provider.swift in Sources */,
81DCC296254257E3007A20DF /* AppDelegate.swift in Sources */,
81DCC298254257E3007A20DF /* SceneDelegate.swift in Sources */,
81B2C2642543884C00F18B79 /* TaskCheckMark.swift in Sources */,
81DCC2CE254260FD007A20DF /* ServiceError.swift in Sources */,
81DCC29A254257E3007A20DF /* ContentView.swift in Sources */,
81DCC2D8254275BF007A20DF /* TaskRowViewModel.swift in Sources */,
81DCC2D4254267CC007A20DF /* TaskListView.swift in Sources */,
F8B4254A2549011900F93641 /* LocalProvider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
81DCC2A4254257E8007A20DF /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F8B42558254910ED00F93641 /* TestLocalProvider.swift in Sources */,
81DCC2AD254257E8007A20DF /* TodoAllTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
2 changes: 1 addition & 1 deletion TodoAll/Model/Entity/Task.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import UIKit

struct Task: Identifiable {
struct Task: Identifiable, Codable {
var id: Int
var title: String
var description: String?
Expand Down
99 changes: 99 additions & 0 deletions TodoAll/Model/Providers/LocalProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// LocalProvider.swift
// TodoAll
//
// Created by Franklin Cruz on 27-10-20.
// Copyright © 2020 S.Y.Soft. All rights reserved.
//

import Foundation
import Combine

class LocalProvider: Provider {
private static var storeNames = [
Task.typeName: "tasks"
]

class func registerStoreName(forType type: Any.Type) {
storeNames[String(describing: type)] = String(describing: type)
}

private func getAllData<T>() -> [T] where T : Identifiable & Codable {
let userDefaults = UserDefaults.standard

guard let storeName = LocalProvider.storeNames[T.typeName] else { return [] }

guard let data = userDefaults.data(forKey: storeName) else { return [] }

let decoder = JSONDecoder()

if let decoded = try? decoder.decode([T].self, from: data) {
return decoded
}

return []
}

private func save<T>(data: [T]) where T : Identifiable & Codable {
let userDefaults = UserDefaults.standard

guard let storeName = LocalProvider.storeNames[T.typeName] else { return }

let encoder = JSONEncoder()
if let encoded = try? encoder.encode(data) {
userDefaults.set(encoded, forKey: storeName)
}
}

func list<T>(filter: [String : Any]?, sort: [String : SortDirection]?) -> Future<[T], ServiceError> where T : Identifiable & Codable {
return Future { promise in
DispatchQueue.global(qos: .background).async {
promise(.success(self.getAllData()))
}
}
}

func get<T>(byId id: T.ID) -> Future<T?, ServiceError> where T : Identifiable & Codable {
return Future { promise in
DispatchQueue.global(qos: .background).async {
let data: T? = self.getAllData().first(where: { $0.id == id })
promise(.success(data))
}
}
}

func create<T>(_ element: T) -> Future<T?, ServiceError> where T : Identifiable & Codable {
return Future { promise in
DispatchQueue.global(qos: .background).async {
var all: [T] = self.getAllData()
all.append(element)
self.save(data: all)
promise(.success(element))
}
}
}

func update<T>(_ element: T) -> Future<T?, ServiceError> where T : Identifiable & Codable {
return Future { promise in
DispatchQueue.global(qos: .background).async {
var all: [T] = self.getAllData()
all.removeAll(where: {$0.id == element.id})
all.append(element)
self.save(data: all)
promise(.success(element))
}
}
}

func delete<T>(byId id: T.ID) -> Future<T?, ServiceError> where T : Identifiable & Codable {
return Future { promise in
DispatchQueue.global(qos: .background).async {
var all: [T] = self.getAllData()
let element = all.first(where: { $0.id == id})
all.removeAll(where: {$0.id == id})
self.save(data: all)
promise(.success(element))
}
}
}
}
29 changes: 29 additions & 0 deletions TodoAll/Model/Providers/Provider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Provider.swift
// TodoAll
//
// Created by Franklin Cruz on 27-10-20.
// Copyright © 2020 S.Y.Soft. All rights reserved.
//

import Foundation
import Combine

enum SortDirection {
case ascending
case descending
}

protocol Provider {
func list<T>(filter: [String:Any]?, sort: [String: SortDirection]?) -> Future<[T], ServiceError> where T: Identifiable & Codable
func get<T>(byId id: T.ID) -> Future<T?, ServiceError> where T: Identifiable & Codable
func create<T>(_ element: T) -> Future<T?, ServiceError> where T: Identifiable & Codable
func update<T>(_ element: T) -> Future<T?, ServiceError> where T: Identifiable & Codable
func delete<T>(byId id: T.ID) -> Future<T?, ServiceError> where T: Identifiable & Codable
}

extension Provider {
func list<T>(filter: [String:Any]? = nil, sort: [String: SortDirection]? = nil) -> Future<[T], ServiceError> where T: Identifiable & Codable {
return list(filter: filter, sort: sort)
}
}
21 changes: 19 additions & 2 deletions TodoAll/Model/Services/TaskService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,27 @@ import UIKit
import Combine

protocol TaskService {
func getTasks( forUser user: Int) -> AnyPublisher<[Task], ServiceError>
var provider: Provider? { get }
func getTasks(forUser user: Int) -> AnyPublisher<[Task], ServiceError>
}

class TaskServiceProvider {
var provider: Provider

init(provider: Provider) {
self.provider = provider
}

func getTasks(forUser user: Int) -> AnyPublisher<[Task], ServiceError> {
return provider.list()
.eraseToAnyPublisher()
}
}

public class MockTaskService: TaskService {
#if DEBUG
class MockTaskService: TaskService {
var provider: Provider? = nil

func getTasks(forUser user: Int) -> AnyPublisher<[Task], ServiceError> {
let mockData = [
Task(id: 1, title: "My Mock task"),
Expand All @@ -28,3 +44,4 @@ public class MockTaskService: TaskService {
.eraseToAnyPublisher()
}
}
#endif
2 changes: 2 additions & 0 deletions TodoAll/UI/TaskCheckMark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ struct TaskCheckMark: View {
}
}

#if DEBUG
struct TaskCheckMark_Previews: PreviewProvider {
static var previews: some View {
HStack {
Expand All @@ -109,3 +110,4 @@ struct TaskCheckMark_Previews: PreviewProvider {
}
}
}
#endif
15 changes: 15 additions & 0 deletions TodoAll/Utils/Identifiable+TypeName.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Struct+TypeName.swift
// TodoAll
//
// Created by Franklin Cruz on 27-10-20.
// Copyright © 2020 S.Y.Soft. All rights reserved.
//

import Foundation

extension Identifiable {
static var typeName: String {
return String(describing: Self.self)
}
}
2 changes: 2 additions & 0 deletions TodoAll/View/TaskListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ struct TaskListView: View {
}
}

#if DEBUG
struct TaskListView_Previews: PreviewProvider {
static var previews: some View {
let mock = MockTaskService()
let vm = TaskListViewModel(service: mock, userId: 1)
return TaskListView(viewModel: vm)
}
}
#endif
2 changes: 2 additions & 0 deletions TodoAll/View/TaskRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ struct TaskRowView: View {
}
}

#if DEBUG
struct TaskRowView_Previews: PreviewProvider {
static var previews: some View {
let mock = MockTaskService()
Expand All @@ -32,3 +33,4 @@ struct TaskRowView_Previews: PreviewProvider {
return TaskRowView(viewModel: vm)
}
}
#endif
Loading

0 comments on commit 319bc2e

Please sign in to comment.