Swift 4 Decodable and List persistence in Realm - realm

After migrating the project to swift 4 I started migrating my object's serialisation from JSON to Realm Object using Decodable. My objects has a list inside that is initialised as well in my init(from decoder: Decoder) method. In code:
class MyObj: RealmSwift.Object, Decodable {
dynamic var id: String
let fooList = List<Foo>()
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
try? container.decode([Foo].self, forKey: .id)
.forEach(fooList.append)
}
override class func primaryKey() -> String? { return "id" }
}
Inspecting the object after the serialisation, everything is fine. all the data are correctly serialised. Then later on in the networking layer the newly serialised object is persisted in realm:
realm.beginWrite()
realm.add(myObjInstance, update: true)
try! realm.commitWrite()
When I re-fetch the object from realm:
realm.object(ofType: MyObj.self, forPrimaryKey: id)
The result is a MyObj instance, but with fooList empty.
Clearly I'm doing something wrong, but what? :D

Related

Realm query sometimes return no data

I am implementing a process to store hundreds of thousands of records and retrieve them by realm query.
The simplified code is as follows
Realm Object
final class Friend: Object {
#objc dynamic var number: Int64 = 0
#objc dynamic var name: String = ""
convenience init(number: Int64, label: String) {
self.init()
self.number = number
self.label = label
}
override class func primaryKey() -> String? {
return "number"
}
}
extension Realm {
static var `default`: Realm {
get {
do {
let realm = try Realm(configuration: RealmConstants.configuration)
return realm
} catch {
return self.default
}
}
}
}
RealmClient class
struct RealmClient {
static func save(_ list: [Friend],
completion: #escaping (Result<Void, Error>) -> Void) {
DispatchQueue.global().async {
autoreleasepool {
do {
let realm = Realm.default
try realm.write {
realm.add(list)
completion(.success(()))
}
} catch {
completion(.failure(error))
}
}
}
}
}
DataStore class (shared file with Call directory extension target)
class DataStore {
let realm = realm
init(realm: Realm.default) {
self.realm = realm
}
var recordcounts: Int {
return realm.objects(Friend.self).count
}
}
However, sometimes the realm query returns 0 records.
Query results
RealmClient.save(friendList) -> friendList is hundreds of thousands of data fetched from server
let dataStore = DataStore()
dataStore.recordcounts -> sometimes return 0
So I have implemented refreshing the realm instance, but it doesn't help.
Like this.
realm.refresh()
Question
I want to be able to always get the data in a query.
How can I implement stable data using a query?
Realm writes are transactional - the transaction is a list of read and write operations that Realm treats as a single indivisible operation.
The reads and writes within the closure {} following the write are part of the transaction; the data is not committed until all of those complete. Transactions are also all or nothing - either all operations complete or none.
Most importantly for this case, transactions are synchronous so adding a callback within the transaction is going to present intermittent results.
Move the callback outside the write transaction
try realm.write {
realm.add(list)
}
completion(.success(()))
It can be illustrated by how we used to write realm transactions:
realm.beginWrite()
write some data
// Commit the write transaction to make this data available to other threads
try! realm.commitWrite() <- data is not valid until after it's been committed
//data has been committed

Downstream SwiftUI view doesn't seem to pick up the Firebase Auth/users collection values from login

I'm trying to store a user profile from a Cloud Firestore Users collection, and have it be instantiated right when logging into the app, and make it editable from a downstream profile view, but I keep running into this " 'self' used before all stored properties are initialized" error.
I have an AuthenticationState class that handles login with Firebase Auth, stores the current logged-in user, and creates a document in my users collection that matches the auth user ID.
It also stores this as a userProfile variable within the authState class.
I think this is probably happening because I'm not storing this in a way that makes sense - I want to have this userProfile be a global variable that can be accessed by any view or View Model, and to make sure that it's instantiated based on the currently logged in Firebase Auth user, no matter where I access it from.
Current Code
I instantiate the authState class directly from my main app, and set it up as an environmentObject from the main view:
#main
struct GoalTogetherApp: App {
let authState: AuthenticationState
init() {
FirebaseApp.configure()
self.authState = AuthenticationState.shared
setupFirebase()
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(authState)
}
}
}
And the class contains these variables (among others). Both loggedInUser and userProfile (which is the one I'm most interested in), start out as nil, and there are no initializers.
Once the sign in functions are called, that triggers loggedInUser to be set, and loggedInUser being set triggers userProfile to be set to the profile of the matching user in the users collection (or create a new one in Firestore if none exists, and set this variable to that):
class AuthenticationState: NSObject, ObservableObject {
// The firebase logged in user, and the userProfile associated with the users collection
#Published var loggedInUser: User?
#Published var userProfile: UserProfile?
static let shared = AuthenticationState()
Then I try to initialize it from the ProfilePage, like so (I'm trying to have email, and then other variables, be editable within the page, and then when they're saved, call back to the Firestore users collection document to update it):
struct ProfilePage: View {
#EnvironmentObject var authState: AuthenticationState
// #State var profile: UserProfile
#State var email: String
init() {
let profile = authState.userProfile!
if profile.email != nil {
_email = State(initialValue: profile.email!)
} else {
_email = State(initialValue: "")
}
}
And I get a " 'self' used before all stored properties are initialized' error on the first line of the init (let profile = ...)
I'm assuming this is because it's creating a new instance of authState where userProfile is not already set. Wondering if there are any best practices for how to store this so I'm able to pick up that value that was set when the user logged in?
The simplest solution is to just give your email variable an initial value when declaring it:
struct ProfilePage: View {
#EnvironmentObject var authState: AuthenticationState
#State var email: String = "" //<-- Here
init() {
let profile = authState.userProfile!
if profile.email != nil {
_email = State(initialValue: profile.email!)
}
}
var body: some View {
TextField("Email", text: $email)
}
}
However, I'd suggest that you may find potential issues with this strategy of doing everything in init. Because SwiftUI views are transient, if you had something that re-rendered the view hierarchy while a user was editing the field on this page, for example, you might find the #State getting reset while editing, for example. To avoid that, you might want to consider setting your values in onAppear:
struct ProfilePage: View {
#EnvironmentObject var authState: AuthenticationState
#State var email: String = ""
var body: some View {
Form {
TextField("Email", text: $email)
}.onAppear {
email = authState.userProfile?.email ?? ""
}
}
}

Response from http post request gets cut in the middle

I'm trying to connect to a webservice that provides some customer data through a POST request but the response gets cut in the middle (or it might be that the trigger function doesn't await the response to complete).
This is done in a flutter environment and the initState() triggers the request.
I have a data service for the customer stuff, CustomerDataService which extends DataService that contain some common stuff such as sending the request and so on.
So in short initState() invoke CustomerDataService.getCustomers(request) which in turn invokes and await DataService.post(endpoint, request).
Http-package: import 'package:http/http.dart' as http;
initState() which is the starting point:
final CustomerDataService _dataService =
new CustomerDataServiceProvider().getCustomerDataService();
#override
void initState() {
_getCustomers();
super.initState();
}
void _getActors() async {
_dataService.getCustomers(
request: new GetCustomersRequest(
navigations: _dataService.suggestedNavigations
),
).then((response) {
_customersResponse = response;
/// Set the state
refresh();
});
}
And then we have the CustomerDataService:
class _CustomerDataService extends DataService implements CustomerDataService
#override
Future<GetCustomersResponse> getCustomers(
{#required GetCustomersRequest request}) async {
String endpoint = createEndpoint(<String>[
App.appContext.identityInstance.rootUrl,
_CUSTOMERS_CONTROLLER,
_GET_CUSTOMERS_ENDPOINT
]);
http.Response res = await post(endpoint: endpoint, request: request.toJson());
if (res.body == null) {
return null;
}
try {
/// This prints an invalid JSON that is cut in the middle
print(res.body);
/// This one naturally throws an exception since res.body isn't valid.
dynamic json = jsonDecode(res.body);
return new GetCustomersResponse.fromJson(json);
} catch (e) {
print("Exception caught when trying to get customers");
print(e);
}
return null;
}
The exception from jsonDecode is
Bad state: No element
And then we have the DataService:
Future<http.Response> post(
{#required String endpoint,
Map<String, String> header,
Map<String, dynamic> request}) async {
if (header == null) {
header = _getHeader();
} else {
header.putIfAbsent(_AUTHORIZATION_KEY, () => _headerAuthorizationValue());
}
http.Response res = await http.post(Uri.parse(endpoint), headers: header);
_validateReponse(res);
return res;
}
I'm clearly doing something wrong but I just can't see it...
The request in DataService.post doesn't add the body (request parameter) in this code and that is another ticket i will file after I've looked more into it, the workaround for now is to change the service to not expect a body.
I've verified that the service behaves as expected with postman.
I hope someone can see where my error(s) is.
Thanks!
Edit 1:
I changed the code a bit so that initState() doesn't use the DataServices created by me but used the http-package directly.
http.post('http://localhost:50140/api/customer/getcustomers').then((res) {
if(res == null) {
print('Response is empty');
}
print('Status code ${res.statusCode}');
print(res.body);
});
super.initState();
}
And the exact same thing happens so I don't think this is due to the dataservices at least.
Edit 2:
Before someone digs to deep into this I just want to say that it doesn't seem to be the response from the service, the http package or the dataservices.
This blog will be updated as soon as I find the cause of the Bad state: no element exception.
Okay, I don't know how to do this but it turns out the question title is incorrect.
It's the terminal that cuts the text when it's too big...
There were no errors in the dataservices or the http package but rather in the conversion from the response body to my strongly typed model, deep down the model tree.
An associative property to the Customer model have an enum, both server- and client side.
The service serialize the enum with the index, the library I use for mapping tries to get the enum by name (case sensitive).
The entity with the problem
#JsonSerializable()
class Item extends Object with _$ItemSerializerMixin
The auto-generated mapping
json['itemType'] == null
? null
: /// I could probably just remove the singleWhere and add [(int)json['itemType']] instead but that would cause some hassle when I run build_runner again.
ItemType.values
.singleWhere((x) => x.toString() == "ItemType.${json['itemType']}")
So, as soon as I did some changes server side (ignored the serialization of the enum and added another property which returned the enum string value instead, to lower case) it started working. I want to look into this further so that I can serialize the enum index instead of the string value and have it mapped that way instead but unfortunately I don't have the time to do it now.
The packages used for the auto-mapping is build_runner and json_serializable.
I'm glad I found a solution, and I'm sorry that the solution turned out to be completely unrelated to the actual post. I hope this can help someone at least.

Is NotNull needed on Kotlin?

I have a class:
class User(
var name: String
)
And a mapped post request:
#PostMapping("/user")
fun test(#Valid #RequestBody user: User) {
//...
}
What if a client will send a JSON of a user with name: null? Will it be rejected by the MVC Validator or an exception will be throwed? Should I annotate name with #NotNull? Unfortunately, I cannot check that because only can write tests (it is not available to create User(null)).
You can avoid using #NotNull, as it'll never get triggered for non-nullable property (bean validation only kicks in after Jackson deserializes the JSON - which is where this will fail).
Assuming you're using jackson-module-kotlin, then you should get an exception with MissingKotlinParameterException as the root cause, which you can then handle in a #ControllerAdvice. In your advice you can handle normal bean validation exceptions (e.g. ConstraintViolationExceptioncaused by #Size) and missing non-null constructor params (MissingKotlinParameterException) and return an API response indicating the fields in error.
The only caveat with this is that the jackson-kotlin-module fails on the first missing property, unlike bean validation in Java, which will return all violations.
Since name is a not-null parameter of User, it cannot accept nulls, ever.
To guarantee that Kotlin compiler:
inserts a null check inside the User constructor which throws an exception if some Java code tries to pass a null.
annotates name with#NotNull in order to stay consistent with Java APIs
does not add any #NotNull annotation since there isn't one which everybody agrees on :(
Here are the corresponding docs
Update
I have rechecked it, and Kotlin compiler v1.0.6 does insert #Nullable and #NotNull from org.jetbrains.annotations. I have updated the answer accordingly.
As I tested, #NotNull doesn't affect on the MVC validation at all. You will only receive a WARN message in console which is normal:
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Instantiation of...
My workarround for now (place the file anywhere):
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
class ExceptionHandler {
#ResponseStatus(BAD_REQUEST)
#ResponseBody
#ExceptionHandler(MissingKotlinParameterException::class)
fun missingKotlinParameterException(ex: MissingKotlinParameterException): Error? {
return createMissingKotlinParameterViolation(ex)
}
private fun createMissingKotlinParameterViolation(ex: MissingKotlinParameterException): Error{
val error = Error(BAD_REQUEST.value(), "validation error")
val errorFieldRegex = Regex("\\.([^.]*)\\[\\\"(.*)\"\\]\$")
val errorMatch = errorFieldRegex.find(ex.path[0].description)!!
val (objectName, field) = errorMatch.destructured
error.addFieldError(objectName.decapitalize(), field, "must not be null")
return error
}
data class Error(val status: Int, val message: String, val fieldErrors: MutableList<CustomFieldError> = mutableListOf()) {
fun addFieldError(objectName: String, field: String, message: String) {
val error = CustomFieldError(objectName, field, message)
fieldErrors.add(error)
}
}
data class CustomFieldError(val objectName: String, val field: String, val message: String)
I use following exception handler:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
class ExceptionHandlerResolver {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
#ExceptionHandler(MissingKotlinParameterException::class)
fun missingKotlinParameterException(ex: MissingKotlinParameterException): MyCustomError? {
return MyCustomError(
timestamp = LocalDateTime.now(),
status = HttpStatus.BAD_REQUEST,
exception = ex,
validationMessages = listOf(
ValidationMessage(
field = ex.path.fold("") { result, segment ->
if (segment.fieldName != null && result.isEmpty()) segment.fieldName
else if (segment.fieldName != null) "$result.${segment.fieldName}"
else "$result[${segment.index}]"
},
message = "value is required"
)
)
)
}
}
It supports exception for any Json path, for example: arrayField[1].arrayItemField.childField
MyCustomError and ValidationMessage classes:
data class MyCustomError(
val timestamp: LocalDateTime,
#JsonIgnore val status: HttpStatus,
#JsonIgnore val exception: Exception? = null,
val validationMessages: List<ValidationMessage>? = null
) {
#JsonProperty("status") val statusCode = status.value()
#JsonProperty("error") val statusReasonPhrase = status.reasonPhrase
#JsonProperty("exception") val exceptionClass = exception?.javaClass?.name
#JsonProperty("message") val exceptionMessage = exception?.message
}
data class ValidationMessage(val field: String, val message: String)

WCF rest service to accept dynamic as parameter

In my application, I am sending a json object to a service and at the service end, I expect an object of type dynamic
public bool TestService(dynamic entity)
{
// process
}
When i debug and see the entity filled, I am not able to type cast it. Any idea how i can retrieve fields from the entity sent
I'm curious - if you're sending up a JSON formatted object, why not have your service method accept a string and then use something like JSON.net to cast it to the appropriate type?
public bool TestService(string entity)
{
var myObject = JsonConvert.DeserializeObject<MyObjectType>(entity);
//do stuff with myObject...
}
Or you could deserialize it into an anonymous object:
public bool TestService(string entity)
{
var myAnonymousObject = new { Name = String.Empty, Address = String.Empty };
var myObject = JsonConvert.DeserializeAnonymousType(entity, myAnonymousObject);
//do stuff with myObject
}
I guess I'm not sure why your JSON formatted object needs to be dynamic.

Resources