Does not conform to protocol Decodabel and Encodable - ios

Can someone tell me what's wrong with my approach? the error is
Type 'User' does not conform to protocol 'Decodable'
Type 'User' does not conform to protocol 'Encodable'
I have tried to replace the null string for Var id, pushId and avatarLink with String.self but no avail either.
Please help
struct User: Codable, Equatable{
var id = ""
var username = String.self
var email = String.self
var pushId = ""
var avatarLink = ""
var status = String.self
static var currentId: String {
return Auth.auth().currentUser!.uid
}
static var currentUser: User? {
if Auth.auth().currentUser != nil {
if let dicctionary = UserDefaults.standard.data(forKey: kCURRENTUSER) {
let decoder = JSONDecoder()
do {
let userObject = try decoder.decode(User.self, from: dicctionary)
return userObject
} catch {
print("Error decoding user from user defaults ", error.localizedDescription)
}
}
}
return nil
}
static func == (lhs: User, rhs: User) -> Bool {
lhs.id == rhs.id
}
}

When you write var username = String.self, the type of the username property will be not String, but String.Type. Basically, it holds not a string, but a type. A type itself is not encodable or decodable, and because of that the whole struct can't be implicitly codable.
If you want username, email and status to contain strings, but not types, but don't want them to have a default value of an empty string (like id or pushId), just declare them as follows: var username: String.
That will enable Swift compiler to synthesize the Codable conformance for you.

Related

Swift Firebase Processing A Custom Object

I am trying to store a struct called 'UnlockingCharacters' in the users document on firebase. I have a struct called 'Character'. When a user taps "unlock" on a character, the 'Character' is added to 'UnlockingCharacters'. I need to store this on firebase in the users document but am struggling to do this.
I have managed to add a 'Character' to 'UnlockingCharacters' and display them in the users profile however it is not stored in firebase so when the app is closed, the 'Character' is no longer in 'UnlockingCharacters'
Here are my structs & classes:
struct Character: Identifiable, Codable {
#DocumentID var id: String?
var character_name: String
var character_type: String
var character_image: String
var character_details: String
var character_usersUnlocking: Int
var character_totalPoints: Int
var user: UserModel?
var didUnlock: Bool? = false
// To identify whether it is being unlocked...
var isUnlocking: Bool = false
}
struct UnlockingCharacters: Identifiable, Codable {
var id = UUID().uuidString
var character: Character
}
class SharedDataModel: ObservableObject {
// Unlocking Characters...
#Published var unlockingCharacters: [Character] = []
}
My functions:
func isUnlocked() -> Bool {
return sharedData.unlockingCharacters.contains { characterData in
return self.characterData.id == characterData.id
}
}
func addToUnlocking() {
if let index = sharedData.unlockingCharacters.firstIndex(where: {
characterData in
return self.characterData.id == characterData.id
}){
// Remove from unlocking...
sharedData.unlockingCharacters.remove(at: index)
}
else {
// Add to unlocking...
sharedData.unlockingCharacters.append(characterData)
}
}
And my UserModel:
struct UserModel: Identifiable, Codable {
var username : String
var pic : String
var bio: String
var uid : String
var id: String { uid }
var activeUnlockingCharacters: [UnlockingCharacters]
}
When trying to process the custom object I get errors:
let ref = Firestore.firestore()
func fetchUser(uid: String,completion: #escaping (UserModel) -> ()){
let db = Firestore.firestore()
ref.collection("Users").document(uid).getDocument { (doc, err) in
guard let user = doc else{return}
let username = user.data()?["username"] as? String ?? "No Username"
let pic = user.data()?["imageurl"] as? String ?? "No image URL"
let bio = user.data()?["bio"] as? String ?? "No bio"
let uid = user.data()?["uid"] as? String ?? ""
do {
try db.collection("Users").document("\(uid)").setData(from: UnlockingCharacters)
} catch let error {
print("Error writing object to Firestore: \(error)")
}
DispatchQueue.main.async {
completion(UserModel(username: username, pic: pic, bio: bio, uid: uid, activeUnlockingCharacters: UnlockingCharacters))
}
}
}
I also get errors in the following line inside my ProfileViewModel:
#Published var userInfo = UserModel(username: "", pic: "", bio: "", uid: "", activeSupportingCharities: [SupportingCharities])
The errors:
Missing argument for parameter 'activeUnlockingCharacters' in call
Cannot convert value of type '[UnlockingCharacters].Type' to expected argument type '[UnlockingCharacters]'
Here is my data structure in the firebase console:
I want there to be a field called UnlockingCharacters in the users data model on firebase when a character is added to the UnlockingCharacters struct.
I think the issue is that your code for writing back to the User document doesn't refer to an instance of UnlockingCharacters , but instead to the type UnlockingCharacters.
So this line:
try db.collection("Users").document("\(uid)").setData(from: UnlockingCharacters)
should probably(*) become
let userModel = UserModel(username: username, pic: pic, bio: bio, uid: uid, activeUnlockingCharacters: unlockedCharacters)
try db.collection("Users").document("\(uid)").setData(from: userModel)
*: probably, because I wasn't sure about your data structure. You might want to post a screenshot of your Firestore data model (in the console) to make it easier to understand how you're intending to store this data.
Also, two other notes:
You probably want to use Codable to replace the manual mapping (let username = user.data()?["username"] as? String ?? "No Username" etc.)
no need to wrap the UI update in DispatchQueue.main.async - Firestore calls back on the main thread already - see https://twitter.com/peterfriese/status/1489683949014196226 .

Confirm to Equatable for Custom struct

I have below kind of response model, where the body is decided by another variable. How can i confirm equatable to this Model
public struct Model {
let type: String? // can be type1 or type2
let body: ResponseType?
}
protocol ResponseType: Codable {
}
struct Response1: ResponseType {
var items: [String]?
}
struct Response2: ResponseType {
var item: String?
}
What i want to achive:
extension Model: Equatable {
public static func == (lhs: Model, rhs: Model) -> Bool {
// How to equate the body?
}
}
When im trying to add Equatable to ResponseType protocol it says below error.
Protocol 'ResponseType' can only be used as a generic constraint because it has Self or associated type requirements
You need to implement == manually. swift doesn't know that body can only be two types, which are?
public struct Model: Equatable {
public static func == (lhs: Model, rhs: Model) -> Bool {
if lhs.type != rhs.type {
return false
}
if let lhsBody = lhs.body as? Response1, let rhsBody = rhs.body as? Response1 {
return lhsBody == rhsBody
} else if let lhsBody = lhs.body as? Response2, let rhsBody = rhs.body as? Response2 {
return lhsBody == rhsBody
} else {
return false
}
}
let type: String? // can be type1 or type2
let body: ResponseType?
}
protocol ResponseType: Codable {
}
struct Response1: ResponseType, Equatable {
var items: [String]?
}
struct Response2: ResponseType, Equatable {
var item: String?
}
It might be easier if you change Model into an enum:
enum Model: Codable, Equatable {
case type1(items: [String]?)
case type2(item: String)
var type: String {
switch self {
case .type1: return "type1"
case .type2: return "type2"
}
}
}
You probably need to change the Codable implementation so that it encodes and decodes the way you want to.

Vapor 4 authentication

Hey I'm having some problems with the login controllers.My code is:
func login(_ req: Request) throws -> EventLoopFuture<UserToken>{
let user = try req.auth.require(User.self)
let token = try user.generateToken()
return token.save(on: req.db).map { token }
}
But I don't really know that how the function work in postman.This is my usermodel :
import Foundation
import Fluent
import Vapor
import FluentPostgresDriver
final class User:Model,Content{
static let schema = "user"
#ID(key: .id)
var id:UUID?
#Field(key:"帳號")
var account:String
#Field(key: "密碼")
var password:String
init() {}
init(id: UUID?=nil, account:String, password:String){
self.id=id
self.account=account
self.password=password
}
}
extension User: ModelAuthenticatable {
// 要取帳號的欄位
static var usernameKey: KeyPath<User, Field<String>> = \User.$account
// 要取雜湊密碼的欄位
static var passwordHashKey: KeyPath<User, Field<String>> = \User.$password
// 驗證
func verify(password: String) throws -> Bool {
try Bcrypt.verify(password, created: self.password)
}
}
extension User {
struct Create: Content {
var account: String
var password: String
var confirmPassword: String // 確認密碼
}
}
extension User.Create: Validatable {
static func validations(_ validations: inout Validations) {
validations.add("account", as: String.self, is: .count(10...10))
// password需為8~16碼
validations.add("password", as: String.self, is: .count(8...16))
}
}
extension User {
func generateToken() throws -> UserToken {
// 產生一組新Token, 有效期限為一天
let calendar = Calendar(identifier: .gregorian)
let expiryDate = calendar.date(byAdding: .day, value: 1, to: Date())
return try UserToken(value: [UInt8].random(count: 16).base64, expireTime: expiryDate, userID: self.requireID())
}
}
And this is my usertoken:
import Foundation
import Vapor
import Fluent
final class UserToken: Content, Model {
static let schema: String = "user_tokens"
#ID(key: .id)
var id: UUID?
#Field(key: "value")
var value: String
// oken過期時間
#Field(key: "expireTime")
var expireTime: Date?
// 關聯到User
#Parent(key: "user_id")
var user: User
init() { }
init(id: UUID? = nil, value: String, expireTime: Date?, userID: User.IDValue) {
self.id = id
self.value = value
self.expireTime = expireTime
self.$user.id = userID
}
}
extension UserToken: ModelTokenAuthenticatable {
//Token的欄位
static var valueKey = \UserToken.$value
//要取對應的User欄位
static var userKey = \UserToken.$user
// 驗證,這裡只檢查是否過期
var isValid: Bool {
guard let expireTime = expireTime else { return false }
return expireTime > Date()
}
}
While I'm typing the value of "account","password" and "confirmPassword", but it kept telling me that "User not authenticated." ,which I've already have the value in my database.
enter image description here
And I'm sure that the password was right. Is there anything that I missed? I'm pretty new in vapor.
And I followed the article below: https://ken-60401.medium.com/vapor-4-authentication-server-side-swift-1f96b035a117
I think the tutorial linked uses HTTP Basic authentication for the login route and I'm guessing that's the case judging by the code shown (it would be good to show how you're registering the login route).
If that's the case then you need to send the username and password in the request as basic authentication credentials in the Authorization header. The value should be Basic <Credentials> where Credentials is username:password Base 64 encoded. However you can get Postman to do it for you

Protocol conforming to Equatable for Diffing

I have a small chat app here.
I can have 2 types of messages:
- text
- video
I am using polymorphism while decoding the JSON like so:
import Foundation
enum MessageType: Int, Decodable {
case text
case video
}
protocol Message: Decodable {
static var type: MessageType { get }
var id: String { get }
var user: User { get}
var timestamp: String { get }
}
struct TextMessage: Message {
static var type: MessageType = .text
var id: String
var user: User
var timestamp: String
let text: String
}
struct VideoMessage: Message {
static var type: MessageType = .video
var id: String
var user: User
var timestamp: String
let text: String
let link: String
let poster: String
}
enum MessageWrapper: Decodable {
enum CodingKeys: String, CodingKey {
case type
}
case text(TextMessage)
case video(VideoMessage)
var item: Message {
switch self {
case .text(let item): return item
case .video(let item): return item
}
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let type = try values.decode(Int.self, forKey: .type)
switch type {
case MessageType.text.rawValue: self = .text(try TextMessage(from: decoder))
case MessageType.video.rawValue: self = .video(try VideoMessage(from: decoder))
default:
throw DecodingError.dataCorruptedError(forKey: .type,
in: values,
debugDescription: "Invalid type")
}
}
}
I am also using the MVVM approach like so:
struct ChatViewModel {
enum ViewModelType {
case loading
case text(TextMessageViewModel)
case video(VideoMessageViewModel)
case failure(ErrorViewModel)
}
enum State {
case initialized
case loading
case loaded([Message])
case failed(Error)
}
let state: State
let viewModels: [ViewModelType]
init(with state: State) {
self.state = state
switch state {
case .initialized:
viewModels = []
case .loading:
viewModels = [
.loading,
]
......
}
}
In order to be able to use a Diffing library like Differ, the ChatViewModel should conform to the Equatable protocol.
extension ChatViewModel: Equatable {
static func == (lhs: ChatViewModel, rhs: ChatViewModel) -> Bool {
return lhs.state == rhs.state
}
}
extension ChatViewModel.State: Equatable {
static func == (lhs: ChatViewModel.State, rhs: ChatViewModel.State) -> Bool {
switch (lhs, rhs) {
case (.initialized, .initialized): return true
case (.loading, .loading): return true
case let (.loaded(l), .loaded(r)): return l == r
case let (.failed(l), .failed(r)): return l.localizedDescription == r.localizedDescription
default: return false
}
}
}
The problem here is for the case let (.loaded(l), .loaded(r)): return l == r, Message, as a protocol, doesn't conform to Equatable.
Making it conform to Equatable like
protocol Message: Decodable, Equatable {
static var type: MessageType { get }
var id: String { get }
var user: User { get}
var timestamp: String { get }
}
produce an error Protocol 'Message' can only be used as a generic constraint because it has Self or associated type requirements for the MessageWrapper:
enum MessageWrapper: Decodable {
...
var item: Message { // Protocol 'Message' can only be used as a generic constraint because it has Self or associated type requirements
switch self {
case .text(let item): return item
case .video(let item): return item
}
}
...
}
Any idea or suggestion to have a clean way to solve this? I saw some post about Type Erasure but after some tests I am not sure that it is actually solving the problem.
You don't have to conform to Equatable in order to be able to use the == operator. You can just define an operator like that yourself, without conforming to Equatable.
For convenience's sake, I'll assume that TextMessage and VideoMessage already conforms to Equatable.
First, write a method that compares Messages:
func messageEqual(m1: Message, m2: Message) -> Bool {
if let textMessage1 = m1 as? TextMessage, let textMessage2 = m2 as? TextMessage {
return textMessage1 == textMessage2
}
if let videoMessage1 = m1 as? VideoMessage, let videoMessage2 = m2 as? VideoMessage {
return videoMessage1 == videoMessage2
}
return false
}
Then a the == operator for [Message]:
func ==(messages1: [Message], messages2: [Message]) -> Bool {
return messages1.count == messages2.count &&
zip(messages1, messages2).allSatisfy(messageEqual)
}
Now l == r should compile.

Social network app gives error on a return line

I was finalizing my social media application and I am consistently having the same error regarding the "username" of my user during the launch process of the application (app is running, user is logged in, and the next view controller fails to come up and it crashes giving EXC_BAD_INSTRUCTION).
I was thinking it might be adata base problem as I had that with the the profile picture, however, the user name is in the database registered as a user with its email and password.
The code of the section the error is in:
import Foundation
import Firebase
import FirebaseDatabase
class Post {
private var _username: String!
private var _userImg: String!
private var _postImg: String!
private var _likes: Int!
private var _postKey: String!
private var _postRef: DatabaseReference!
var username: String
{
return _username
}
var userImg: String
{
return _userImg
}
var postImg: String {
get {
return _postImg
} set {
_postImg = newValue
}
}
var likes: Int {
return _likes
}
var postKey: String {
return _postKey
}
init(imgURl: String, likes: Int, username: String, userImg: String) {
_likes = likes
_postImg = imgURl
_username = username
_userImg = userImg
}
init(postKey: String, postData: Dictionary<String, AnyObject>){
_postKey = postKey
if let username = postData["username"] as? String {
_username = username
}
if let userImg = postData["userImg"] as? String {
_userImg = userImg
}
if let postImg = postData["imageUrl"] as? String{
_postImg = postImg
}
if let likes = postData["likes"] as? Int {
_likes = likes
}
_postRef = Database.database().reference().child("posts").child(_postKey)
}
func adjustLikes(addLikes: Bool) {
if addLikes {
_likes = likes + 1
} else {
_likes = likes - 1
}
_postRef.child("likes").setValue(_likes)
}
}
the line where the error occurs:
return _username
I am just really puzzled what the issue could be. I have looked at all the IBOutlets, as well as removing and adding new users. I would appreciate any help.
In the init(postKey:postData:) constructor it's not guaranteed that the _username property will be set. However, the public username property is of a non-optional type String. My assumption is that the username getter tries to forcefully unwrap a nil value.
Your username variable is a computed read only property which is returning value from variable _username:String!(Forced unwrapped value means can not be nil else crash ) .You need to be sure that your variable is not nil.
example when _username is not nil-:
class Foo{
var _username:String = "tushar"
var username: String
{
return _username
}
}
var object = Foo()
print(object.username)
Example when your variable can get nil value-:
class Foo{
var _username:String!
var username: String
{
print(_username)
return _username
}
}
var object = Foo()
print(object.username)
If variable has no value in it that's a crash

Resources