Conversation
MOD changed SwiftLint line length warning to 150
…rsion uses 120 per default
return children | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should add this as a public method on Component
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it shouldn't be needed (so shouldn't be exposed) but maybe I'm missing the point to use it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will keep it there for now
Current coverage is 99.16% (diff: 100%)@@ master #7 diff @@
==========================================
Files 7 8 +1
Lines 214 240 +26
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 212 238 +26
Misses 2 2
Partials 0 0
|
I think we should update the code (JSONReaderTests, app-structure.json, ..) first, so that it fits to our decision to get rid of the id for now. |
@AndreasThenn tests are already updated to check if we assert when no |
@joanromano JSONFactory, StackClusterFactory, TabBarClusterFactory cannot be intialized from outside the module because they have no public initializer. |
App Example can be found in feature/jsonParsingExample. Will create a PR after this is finished. |
@mathiasAichinger good catch, fixing and updating |
let faultyProduceNoKeys = { _ = try! jsonFactory.produce(from: ["foo": "bar"]) } | ||
let faultyProduceNoType = { _ = try! jsonFactory.produce(from: ["id": "bar"]) } | ||
|
||
expect(faultyProduceNoKeys()).to(throwAssertion()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should not throw an assertion. throws an error. However it's now asserting because you are try!
ing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true, forgot to update after updating error handling
@@ -15,7 +15,7 @@ class ComponentTests: QuickSpec { | |||
|
|||
override func spec() { | |||
|
|||
typealias DictMeta = Dictionary<String, String> | |||
typealias DictMeta = [String:String] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good but missing a space after :
@@ -112,7 +112,7 @@ class StackClusterTests: QuickSpec { | |||
labelComponent(title: "1", color: .red, labelSize: size), | |||
labelComponent(title: "2", color: .red, labelSize: size), | |||
labelComponent(title: "3", color: .red, labelSize: size), | |||
labelComponent(title: "4", color: .red, labelSize: size), | |||
labelComponent(title: "4", color: .red, labelSize: size) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like trailing comma in collections because you can easily add or move lines... I know swiftlint complains with recent versions but we might disable the rule eventually
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually agree with swiftlint here :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am fine with both solutions, but I would choose the one that differs not from the default ruleset, so we can avoid custom rules wherever it is possible.
/// - children: an array of children component | ||
/// - meta: a ComponentMeta object | ||
/// - Returns: An optional Component | ||
func produce(children: [Component], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a fan of this naming... produce...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestions?
|
||
/// A concrete ComponentFactory which produces a stack cluster component | ||
public final class StackClusterFactory: ComponentFactory { | ||
public func produce(children: [Component], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems to me that these factories have something wrong.
To comform to ClusterFactory you need a function that given children and meta creates a Component
then ClusterLayout.stack...
ClusterLayout.tab...
are already factories so we are creating all this code to basically wrap those functions.
Other than this this factory implementation seems wrong to me because it's only working for .cluster
How do you create wrapper and view from json? Am I missing anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the proof of concept implementation I had 3 factories (one for cluster one for wrapper one for view) because each type has different inputs.
Also all this XXXFactory
seems not need to me since all we want is a function that given the correct input returns a Component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Factories are meant to provide the type
to register. Maybe we can skip that and just have JSONFactory registering the functions for a given string, but I liked the idea of representing a factory also.
In the tests I create factories for clusters and views, and I guess would also work for wrappers right?
So if I am not wrong the difference between this and the proof of concept is that here we are more explicit on the factories (which also needs more code, true) and in the proof of concept the factory only registers closures for given types.
let children = json[childrenKey] as? [JSONObject] ?? [] | ||
let componentChildren = try children.flatMap { try produce(from: $0) } | ||
|
||
return factories[type]?.produce(children: componentChildren, meta: meta) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes looks like only cluster can be created from json
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the factory tests we create clusters or plain views, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well you can by ignoring the children (empty array) , the view shouldn't have children.
And what about wrappers? They don't have children but one child
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You would get the first child of the children. So for you what's wrong here is having same factory API for the three kind of Component
's
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes and actually you don't need three factory (will write some pseudo code when I arrive at home)
Having to ignore children or get the first seems bad API design.
Other than this, defining types that wraps functions (e.g. StackFactory) makes registration way more verbose than needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying out that approach too, let's see what we each get
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got something out of what you said, I will update the tests now and we can discuss the differences
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote some pseudocode:
typealias JSONRepr = [String: Any]
fileprivate protocol FactoryBuilder {
var factory: (JSONRepr) -> Component { get }
}
fileprivate class ClusterFactoryBuilder: FactoryBuilder {
let factory: (JSONRepr) -> Component
init(builder: @escaping Component.ClusterBuilder) {
self.factory = { (json) in
return Component.cluster(
builder: builder,
children: json["children"] as! [Component],
meta: json["meta"] as! [String: Any]
)
// pseudo.....
}
}
}
// same for....
// fileprivate class WrapperFactoryBuilder: FactoryBuilder
// fileprivate class ViewFactoryBuilder: FactoryBuilder
class ComponentFactory {
private static var factories: [String: FactoryBuilder] = [:]
class func registerCluster(for id: String, builder: @escaping Component.ClusterBuilder) {
factories[id] = ClusterFactoryBuilder(builder: builder)
}
class func registerWrapper(for id: String, builder: Component.WrapperBuilder) {
// factories[id] = WrapperFactoryBuilder(builder: builder)
}
class func registerView(for id: String, builder: Component.ViewBuilder) {
// factories[id] = ViewFactoryBuilder(builder: builder)
}
class func component(for json: JSONRepr) -> Component? {
let id = json["id"] as! String // pseudo
return factories[id]?.factory(json)
}
}
// USAGE
ComponentFactory.registerView(for: "myView") { (meta) -> UIViewController? in
return UIViewController()
}
ComponentFactory.registerCluster(for: "stack") { (children, meta) -> UIViewController? in
return StackViewController()
}
ComponentFactory.registerCluster(for: "tabvar", builder: tabBarBuilder(Bundle.main))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got something similar with minor differences, i will push and we can check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this approach now, because it's using closures everywhere. The first approach was a little bit a mixture of the closure and class approach. I still prefer the class approach, but probably because I'm thinking in old concepts ;) (I would have used classes for the clusters and protocols for the factories.)
@MP0w refactored following a similar approach, doing the json handling within the json factory. Also using the factory as an instance with instance methods instead of static methods with a single static builder registration: still not sure which is the best solution there. |
/// - bundle: the bundle where the file is located | ||
/// - Returns: an optional serialized JSONObject | ||
/// - Throws: throws an error in case of failure or invalid JSON data | ||
public class func jsonObject(from jsonFilename: String, bundle: Bundle = .main) throws -> JSONObject? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this shouldn't be part of Matrioska, I think the whole JSONReader
shouldn't be part of Matrioska since it's just convenience functions for JSONSerialization
and retrieving a file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this class to the test target for now. If we need it later on we can move it again to the project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is still in Source, should be in Test/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't apply any changes yet, was waiting for the full review
|
||
opt_in_rules: | ||
- empty_count | ||
- force_unwrapping |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why removed all these rules?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the swiftlint for tests so I disabled them only here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true!
Overall my feelings:
I would go for the ease of use of the api then and keep the second approach. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't properly check the test because I think we still need to define the json schema properly
@@ -9,7 +9,7 @@ | |||
import UIKit | |||
import Matrioska | |||
|
|||
struct TileConfig: MaterializableComponentMeta { | |||
struct TileConfig: ExpressibleByComponentMeta { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe setup buddybuild quickly so we can make sure to not break the example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do so
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (wanted to see how it works), I created a runtastic team on buddybuild. Will invite you guys. Btw I think buddybuild also supports libraries now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh nevermind that's only for payed plans.
|
||
static let typeKey = "type" | ||
static let metaKey = "meta" | ||
static let childrenKey = "children" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to son specs and documentation (schema would also be nice)... because I think we are missing child
for wrapper
for example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now we agreed on having the same children
key for both wrapper and cluster afaik
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, as said at the beginning of the PR the documentation is still to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't agree with using children instead of child. If we want to have wrappers (and I think we want) they shouldn't be an hacky cluster (a cluster that ignore children other than the first.
Please couldn't you guys reconsider this decision or try to explain why you think it should be like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a discussion for #3 probably. Anyway nobody said we don't want to have wrappers, it's just the agreement on the json schema.
/// A factory that wraps ComponentFactory objects and uses them to produce Components | ||
public final class JSONFactory { | ||
|
||
static let typeKey = "type" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all these properties should be private...
Also consider a nested enum for namespacing:
enum Keys { // not a struct because an enum with no cases can't be initialized
static let type = "type"
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but what would the nested enum namespacing bring if we make it private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usage would be Keys.type instead of JSONFactory.type .
More explicit imho
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also thought it might be good for these keys to be public so that clients now which keys to expect. That could also be reflected on the documentation though.
let componentChildren = try children.flatMap { try component(from: $0) } | ||
var componentResult: Component? | ||
|
||
if let viewFactory = viewFactory[type] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would do the matching differently:
let id = ....
if let children = ... { // is optional
// then is a cluster
} else if let child = ... {
// then is a wrapper
} else {
// must be a view
}
But this depends by the json specs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now we would have the same key for children
in the json
/// - bundle: the bundle where the file is located | ||
/// - Returns: an optional serialized JSONObject | ||
/// - Throws: throws an error in case of failure or invalid JSON data | ||
public class func jsonObject(from jsonFilename: String, bundle: Bundle = .main) throws -> JSONObject? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is still in Source, should be in Test/
|
||
opt_in_rules: | ||
- empty_count | ||
- force_unwrapping |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true!
return children | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it shouldn't be needed (so shouldn't be exposed) but maybe I'm missing the point to use it?
/// - Throws: `JSONFactoryError` when a mandatory key is missing | ||
public func component(from json: JSONObject) throws -> Component? { | ||
guard let type = json[Key.type] as? String else { | ||
throw JSONFactoryError.missing(json, Key.type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there should be a possibility to register fallback components. So eg. for clusters always fallback to stacks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mentioned this on last meeting and they said we don't include fallback components for now? We could implement it here though.
SwiftLint found issuesWarnings
Generated by 🚫 danger |
07b8514
to
abd0a79
Compare
Closes #3
First approach to JSON parsing and factories.
JSONFactory
wraps registered factory builders (closures which provideComponent
s) and uses them in order to parseJSONObject
s and create the mainComponent
Still to do:
@MP0w @mathiasAichinger @AndreasThenn let's do this!