How to access custom channel extra data from iOS client - ios

Currently our backend has added a dict on channel object as part of the extra data, it looks something like this:
{
// channel stuff from Stream
"extra_data": {
"custom dict": {
"custom field": "custom value"
}
}
}
However it seems that we cannot access to that dict from the iOS client since the channel.extraData type is a ChannelExtraDataCodable which only has two properties: name and imageURL.
Is there a way to access to this custom stuff from the client side?
Thanks in advance.

You need to define your own structure conforming to ChannelExtraDataCodable and set Channel.extraDataType to it.
Example:
struct MyChannelExtraData: ChannelExtraDataCodable {
var name: String?
var imageURL: URL?
var customDict: [String: String]
}
// Before you initialize the client
Channel.extraDataType = MyChannelExtraData.self
For more information on this, you can check the Stream Chat's documentation about custom extra data on iOS.

Related

How to add a description to a Data instance in Swift

I wanted to add a description to my Data instances so I know what they're meant to be used for.
Eg: "This data is for (user.name)'s profile picture" or "This data is a encoded User instance" etc.
I was going through the properties available to Data instances and saw there was a description and debugDescription property. I tried to set the values for those properties but it seems like I can't since they are get-only properties. Is there any other way to add a description to a Data instance?
Edit:
Wrapping a Data instance as recommended below is a great solution but if there's a way to achieve the same without using a wrapper please let me know.
You can create a light-weight wrapper like following -
import Foundation
struct DescriptiveData: CustomStringConvertible, CustomDebugStringConvertible {
let data: Data
let customDescription: String?
init(data: Data, customDescription: String? = nil) {
self.data = data
self.customDescription = customDescription
}
var description: String { customDescription ?? data.description }
var debugDescription: String { description }
}
Usage
let data = DescriptiveData(data: Data(), customDescription: "data for profile picture")
print(data)
// data for profile picture

Storing collection of custom objects where each I stored in keychain

I have a password managing app. Currently, I store all of the user's accounts (i.e. Netflix, Spotify, etc) in an array that is then stored in User Defaults. Now, I want to make things more secure by storing each account in iOS keychain. I realized that even if I store each account in keychain, I will still need to keep track of them all in a data structure (for populating tableviews, etc) and that data structure will need to be stored somewhere. I am struggling to understand the best way to go about implementing this.
My current design approach is to have:
• An array of custom Account objects stored in User Defaults
• each Account in the array is stored in keychain
I am using Locksmith for working with keychain (Note: I'm not married to this framework).
Locksmith requires me to implement several protocols in my Account class. This is then confusing me when it comes to trying to encode Account objects into the master array being stored in User Defaults. I am trying to make Account conform to Codable and am royally confused about what is going/needs to go on on this point.
class Account: ReadableSecureStorable,
CreateableSecureStorable,
DeleteableSecureStorable,
GenericPasswordSecureStorable,
Codable {
var username: String // can i make these let's
var password: String
// Required by GenericPasswordSecureStorable
let service: String
var account: String { return username }
// Required by CreateableSecureStorable
var data: [String: Any] {
return ["password": password]
}
init(service: String, username: String, password: String) {
self.username = username
self.password = password
self.service = service
}
private enum CodingKeys: String, CodingKey {
case username
case password = "secret"
case service
}
}
struct AccountDefaults {
static private let accountsKey = "accountsKey"
static var accounts: [Account] = {
guard let data = UserDefaults.standard.data(forKey: accountsKey) else { return [] }
return try! JSONDecoder().decode([Account].self, from: data)
}() {
didSet {
guard let data = try? JSONEncoder().encode(accounts) else { return }
UserDefaults.standard.set(data, forKey: accountsKey)
}
}
}
I was getting errors stating that Account does not conform to decodable before adding the codingKeys enum.
From here on, I can't figure out where to go.
When I add an account, I can store it with Locksmith. Will the default codingKeys password override the real password I'm trying to save into keychain?
I know I'm not supposed to include multiple questions here, but the thoughts going through my head are:
What do I need to encode/decode?
How does using Locksmith affect what I need to encode/decode?
Will the password need to be encoded?
Codable is just an alias for Decodable & Encodable
where Decodable and Encodable are protocols.
So,
What do I need to encode/decode?
If you mean JSON,
For converting into JSON use default implementations. For simple structs just allow this protocol conformance.
How does using Locksmith affect what I need to encode/decode? Regarding this – no effect.
Will the password need to be encoded?
your password is a string. Obviously, we be easily encoded into JSON.
If you mean the password security, the Keychain with 'only unlocked' level is good.

MultipeerConnectivity MCPeerID does not conform to Codable

I'm writing a simple P2P chat program and thus far I've been storing MCPeerID's displayName property as a string in my model to determine who to send a particular message to. On each send operation, I search through the connectedPeers array and copy the MCPeerID into a receiver list whenever the displayName matches the string I have in my model.
This can be problematic in the case where two peers have the same name. And I'm also not satisfied with having perform a search for each send. So I'm trying to use MCPeerIDs directly in my model. However, Xcode complains that MCPeerID does not conform to Encodable nor Decodable, and I'm not sure how to fix this.
My model represents a topic that maintains a list of participants as well as a log of who said what. Thus, I can sync a new participant when they join and update existing participants when new messages are added. My model looks something like the following:
import Foundation
import MultipeerConnectivity
class Task : Codable {
var uuidStr: String = UUID().uuidString
var topic : String
var history : [String] = []
var users : [MCPeerID] = []
...
private enum CodingKeys: String, CodingKey {
case uuidStr
case topic
case history
case users
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(uuidStr, forKey: .uuidStr)
try container.encode(topic, forKey: .topic)
try container.encode(history, forKey: .history)
try container.encode(users, forKey: .users)
}
...
}
(I haven't shown the standard init() as this is not where the problem lies.)
After looking at the documentation, I see a method with the signature MCPeerID.init?(coder: NSCoder) and func encode(with: NSCoder) but I'm not sure how to specify the NSCoder parameter. Any help on how to serialise the users property would be much appreciated.
There seem to be a few issues here (p2p is a complex subject):
1. identifying users (business level),
The peer name MCPeerID(displayName: ) is not the peer identifier, it's just aimed at the human viewer (e.g. chatroom users). Point 3) below shows a way how a peer can include other user info when advertising their presence.
2. identifying peers (network level),
The framework (MC) creates a unique peer id for you and recommends the app re-use (ie. persist & reload) across app launches (MCPeerID - official docs - example objetive-c using NSUserDefaults storage api).
3. sharing peer session info (session level)
In your model, you're serialising the Task, fine (though no mention of what format, .xml, .plist, .json etc), but I don't see why you're wanting to serialise these MCPeerIDs?
Each peer on connecting is supposed to re-use their own peerID, and, optionally, if they wish to advertise additional info to potential peers they could provide a dictionary with info about the business-user behind the peer object e.g:
myAdvertiser = MCNearbyServiceAdvertiser(peer: reusableLocalPeerID, discoveryInfo: ["uuidStr" : "my-user-id-here"], serviceType: "my-tasks-app")
(MCNearbyServiceAdvertiser docs).
4. separating concerns (data level)
I would separate the framework (MC) from business model (Task, User ...) , to something like this:
class Task: Codable { var users: [User] ... }
class User: Codable { uid:String ...}
struct PeerRecord: { var peer:MCPeerID, var user_id:String, var name:String ... }
var foundPeers:[MCPeerID]
var connectedPeers:[String:PeerRecord] // e.g. ["a-user-id": PeerRecord(peer: ...)]
5. serialising
Apple example at point 2) above shows how to serialise MCPeerID using UserDefaults api.
Generally, when one extends Codable, one also expects to use an encoder (JSON, XML etc) somewhere in the app,
passing to this encoder's .encode(...) method the object in question e.g:
func storeTasks(tasksCodable:[Task]) -> Bool {
if let encoded_in_data_format = try? JSONEncoder().encode(tasksCodable) {
UserDefaults.standard.set(encoded_in_data_format, forKey: "tasks")
return true
}
return false
}

How can i have custom (Computed) property in objectmapper?

I am using Objectmapper and Realm for my project.
I have an object like following
class File
{
dynamic var name
dynamic var folder
dynamic var path // This is not coming from JSON // this should be combination of both name+folder
}
I thought of writing a computed property to achieve this but Realm does not support computed properties as primary key.
But I should use this as primary key. Is there any way I can manipulate to add that value after coming from server response.
Note: I am using AlamofireObjectMapper.
I am using the following method which parses the server response and gives me the model object.
Alamofire.request(router).responseObject{ (response: DataResponse<T>) in
{
let myModel = response.result.value // Parsed object
===== What can i do here to achieve my requirement=====
}
You should really consider having some kind of id as the primary key and not computing it from other properties (what happens if they are empty or the computation goes wrong? You'd be left without a valid primary key).
However, if you really need to, you could try
let realm = try Realm()
try realm.write {
items.forEach({ (item) in
item.path = item.name + item.folder
}
realm.add(items, update: true)
}
and don't forget to define path as the primary key in the File class:
class File
{
dynamic var name
dynamic var folder
dynamic var path // This is not coming from JSON // this should be combination of both name+folder
override static func primaryKey() -> String? {
return "path"
}
}

Add arrays to Realm with swift 3

I'm new in Realm and I tried to add an Array as I did with strings and I ended up with some errors. So after a little search I found out a solution:
class Sensors : Object {
dynamic var name = ""
dynamic var message = ""
var topic: [String] {
get {
return _backingNickNames.map { $0.stringValue }
}
set {
_backingNickNames.removeAll()
_backingNickNames.append(objectsIn: newValue.map({ RealmString(value: [$0]) }))
}
}
let _backingNickNames = List<RealmString>()
override static func ignoredProperties() -> [String] {
return ["topic"]
}
}
class RealmString: Object {
dynamic var stringValue = ""
}
This is working very good, now I want to add another array inside this class.
If someone knows any other ways to add arrays with realm please share it.
Thanks in advance
As a general rule it's way more efficient to use the one-to-many relationships provided by Realm instead of trying to emulate them by using arrays (Realm's collections are lazy, the objects contained are instantiated only when needed as opposed to plain Swift arrays).
In your case, if I understand correctly what you're trying to do, you want to add [RealmString] Swift arrays to the _backingNickNames list.
Why not use the append(objectsIn:) method of Realm's List class (see here), like this:
// Dog model
class Dog: Object {
dynamic var name = ""
dynamic var owner: Person?
}
// Person model
class Person: Object {
dynamic var name = ""
dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
let dogs = List<Dog>()
}
let jim = Person()
let dog1 = Dog()
let dog2 = Dog()
// here is where the magic happens
jim.dogs.append(objectsIn: [dog1, dog2])
If you want to do the opposite (convert from a List to an Array) just do :
let dogsArray = Array(jim.dogs)
• • • • • • • •
Back to your own posted solution, you could easily refactor the model to accommodate this. Each Sensor object could have several Topic and several Message objects attached.
Just ditch the message and topic computed properties and rename topicV and messageV to topics and messages respectively. Also rename RealmString to Topic and RealmString1 to Message.
Now, you could easily iterate through the, say, topics attached to a sensor like this :
for topic in sensor1.topics { ... }
Or if you want to attach a message to a sensor you could do something like this (don't forget to properly add the newly created object to the DB first):
let message1 = Message()
message1.stringValue = "Some text"
sensor2.messages.append(message1)
So, no need to use intermediary Swift Arrays.
After testing I managed to add another array like that:
class Sensors : Object {
dynamic var type = ""
dynamic var name = ""
dynamic var badge = 0
var topic: [String] {
get {
return topicV.map { $0.stringValue }
}
set {
topicV.removeAll()
topicV.append(objectsIn: newValue.map({ RealmString(value: [$0]) }))
}
}
var message: [String] {
get {
return messageV.map { $0.stringValue1 }
}
set {
messageV.removeAll()
messageV.append(objectsIn: newValue.map({ RealmString1(value: [$0]) }))
}
}
let topicV = List<RealmString>()
let messageV = List<RealmString1>()
override static func ignoredProperties() -> [String] {
return ["topic", "message"]
}
}
class RealmString: Object {
dynamic var stringValue = ""
}
class RealmString1: Object {
dynamic var stringValue1 = ""
}
What bogdanf has said, and the way you've implemented it are both correct.
Basic value types aside, Realm can only store references to singular Realm Object objects, as well as arrays of Objects using the List type. As such, if you want to save an array of types, it's necessary to encapsulate any basic types you want to save (like a String here) in a convenience Realm Object.
Like bogdanf said, it's not recommended to convert Realm Lists to standard Swift arrays and back again, since you lose the advantages of Realm's lazy-loading features (which can cause both performance and memory issues), but memory issues can at least be mitigated by enclosing the code copying data out of Realm in an #autoreleasepool block.
class MyObject: Object {
dynamic var childObject: MyObject?
let objectList = List<MyObject>()
}
So in review, it's best practice to work directly with Realm List objects whenever possible, and to use #autoreleasepool any time you do actually want to loop through every child object in a Realm. :)

Resources