store chat history in ios - xmpp

We are using Amazon Ubuntu Server, Apache and PHP Installed Openfire of version 4.0.1 and the app configurations are XCode 7.2 by using Swift and library we are using is XMPP Framework of version 3.6.6.
We can able do single text chat, but cannot store chat history - How can we maintain chat history and retrieve that.

Create a XCMODEL schema file of core data in your project
Add your entities in that model.
That can be in your case
MESSAGE entity
CONTACT entity
etc (whatever you want to save )
For each entity in your DB model, you can now add "Attributes" in your Entity (say MESSAGE entity). Attributes can be created time, message text etc.
Make a NSObject extended class which have properties same as your attributes
.h file
#property (nonatomic, retain) NSNumber * createdAt;
.m file
#dynamic createdAt;
Now you can use : Core data operation methods like saving NS managed object etc to save or other operation on DB.
For Example : to save a contact in your DB with full name attribute.
DBHandler * dbHandler = [DBHandler sharedInstance];
DB_CONTACT* existingContact = [self getContactByKey:#"userId" value:[userContact userId]];
if (existingContact) {
return false;
}
BOOL result = NO;
DB_CONTACT * contact =
[NSEntityDescription
insertNewObjectForEntityForName:#"DB_CONTACT"
inManagedObjectContext:dbHandler.managedObjectContext];
contact.fullName = userContact.fullName;
NSError *error = nil;
result = [dbHandler.managedObjectContext save:&error];
if (!result) {
NSLog(#"DB ERROR :%#",error);
}
return result;
Method responsible to save in DB:
[NSEntityDescription
insertNewObjectForEntityForName:#"DB_CONTACT"
inManagedObjectContext:dbHandler.managedObjectContext];
Hope this gives you an idea. For more details check https://github.com/AppLozic/Applozic-iOS-SDK out as sample app for messaging.

If you want to maintain chat local history, you can use coredata to save all incoming and send messages on the right order.
To restore the chat history, use parameters like XMPPUserName (from and to) from your coredata.
To see the history on a openfire server, you can:
Install the Monitoring Server plugin
Go to the tab “Archiving”
Select their settings and enable "Archive one-to-one chats”

Related

Retrieve core-data model version from appDelegate

Anybody knows how to get the core-data model version when the app starts?
I've implemented the lightweight migration and it works fine but I need to check if the sqlite db is based to the old model version before the migration process starts.
In the new model I added a new entity but I need to populate it with value of an entity of the old model. I want to do that only one time.
Is there a way to do it in the appdelegate?
Thanks,
Max
I solved this by setting the DB-version in my preferences (.plist). So if I change the DB, I set the new DB-version in there and store the version from the last start in the NSUserDefaults.
NSNumber *newDbVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"DBVersion"];
NSNumber *oldDbVersion = [[NSUserDefaults standardUserDefaults] objectForKey:#"DBVersion"];
if (oldDbVersion == nil)
{
// part without handling, so you know the max db version.
}else
{
if([oldDbVersion intValue] < [newDbVersion intValue])
{
// try the liteweight migration and if that failed, migrate by hand
}
}

Best way to check existent data in the database (iOS)

I'm developing an app that manages messages, and I want the app connects to the server, get messages and save them in the database(coredata). If the messages already exist, doesnt do anything and if they dont, add them to the database.
I'm thinking some ways to do it but I don't know exactly what to do. Any help? Thanks in advance
I would recommend using Restkit framework http://restkit.org
Reskit provides integration with Core Data.
Pros of using Restkit:
- Combines HTTP request/responses API, along with object mapping, offline/caching support with Core Data, all in one framework
- Object Mapping means that you're writing clean code, you define your classes and how they map to the JSON attributes, then you GET/POST/DELETE with few lines of code after that
- Core Data support means that your projects can work offline, data is sync when working online, but persistent when you need it offline
- The framework is well maintained
Cons:
- Works only with JSON REST APIs
- There can be a steep learning curve for some aspects
- Can be challenging if you work with REST APIs that are not completely 'standard'
The simplest way is to add a guid attribute (an identifier of type NSString, for example) to the entity you are interested in and check for that guid when you import data.
Here, you have two ways: let the server generate the guid for you or implement your own algorithm in the client side (iPhone, iPad, etc.). In both cases you need to be sure the guid is unique for each message.
So, for example, suppose the server generates the messages (and each message has its own guid). When you import data you also save the guid for each message object. If you have already a message with a specific guid, you don't add it, otherwise you add it. This could be done using the Find-or-Create pattern (see Implementing Find-or-Create Efficiently).
Hope that helps.
This is simple, it took me sometime to learn this, I use it in most of my apps.
First you need an ID of the fetched item, for example messageID.
When you fetch the JSON with all the items, for example using AFNetworking, you're going to receive an array of objects in NSDictionaries.
Before parsing the item load all the IDs of your stored items in a NSMutableDictionary (key => messageID, value objectID, this is related to the Core Data fault).
Don't forget to init the NSMutableArray somewhere:
_dictionaryOfEventIDAndObjectID = [NSMutableDictionary dictionary];
- (void)prepareDictionaryOfMessageIDs
{
[self.dictionaryOfEventIDAndObjectID removeAllObjects];
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Message"];
[fetchRequest setResultType:NSDictionaryResultType];
NSExpressionDescription *objectIDDescription = [[NSExpressionDescription alloc] init];
objectIDDescription.name = #"objectID";
objectIDDescription.expression = [NSExpression expressionForEvaluatedObject];
objectIDDescription.expressionResultType = NSObjectIDAttributeType;
[fetchRequest setPropertiesToFetch:#[objectIDDescription, #"messageID"]];
NSArray *objectsDict = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSDictionary *objectDict in objectsDict) {
[self.dictionaryOfMessageIDAndObjectID setObject:[objectDict valueForKeyPath:#"objectID"] forKey:[objectDict valueForKeyPath:#"messageID"]];
}
}
Then in the fetched data completion block just add something like this:
for (NSDictionary *objectDict in objectsDict) {
NSString *fetchedID = [objectDict objectForKey:#"id"];
if ([self.dictionaryOfMessageIDAndObjectID objectForKey:fetchedID]) {
continue;
}
[self parseMessageFromDictionary:objectDict];
}

Saving data using local storage on an iPhone - which way to go?

I'm building a web-app which is communicating with a database. I want the web-app to download all info from my database and then save it locally so it can be accessed and edited (even if the user is offline) locally and then synced back to my database (when the user is online).
Every row in my database consists of 2 strings and 3 integers and I've got around 1000-1500 rows in my database (that might give you and idea about the size of my db).
So which is the best way to go? Is it possible to cache this kind of data on the users device somehow? Like retrieving the database via JSON and than storing it locally in a javascript array which the user can access and work on (as mentioned: the later part should be able to work even if the user is offline). All input on this is greatly appreciated. Am I at least thinking in the right direction here?
EDIT: I just want to clarify that I'm working with a web app. An app devoloped with HTML, CSS and Javascript. I'm not doing a native objective C iOS app.
make a class of NSObject
#interface abc : NSObject<NSCoding>
#property..... // add properties that you want to save
-(void)initWithDictionary:(NSMutableDictionary *)dictionary; // override init by passing dictionary with values of your properties
#end
in abc.m file
implement these functions
-(void)initWithDictionary:(NSMutableDictionary *)dictionary
{
yourProperty=[dictionary valueForKey:#"propertyKey"];
}
-(void) encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:yourProperty forKey:#"propertyKey"];
}
-(id) initWithCoder:(NSCoder *)aDecoder{
self =[super init];
if(self){
[self setyourProperty:[aDecoder decodeObjectForKey:#"yourProperty"]];
}
return self;
}
now make a shared class that can be accessed from anywhere
#interface xyz : NSObject
{
NSMutableArray *allItems;
}
+(xyz *) sharedStore;
-(NSMutableArray *)allItems;
-(abc *) createItem:(NSMutableDictionary *)dictionary;
-(NSString *) saveItemPath;
-(BOOL)saveChanges;
now in xyz.m implement these functions
-(id)init{
self=[super init];
if(self){
NSString *path=[self saveItemPath];
allItems=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
if(!allItems){
allItems=[[NSMutableArray alloc]init] ;//]WithObjects:#"No more item in store", nil];
}
}
return self;
}
+(xyz *)sharedStore{
static xyz *sharedStore=nil;
if(!sharedStore){
sharedStore=[[super allocWithZone:nil]init];
}
return sharedStore;
}
+(id)allocWithZone:(NSZone *)zone{
return [self sharedStore];
}
-(NSArray *)allItems{
//[allItems insertObject:#"No more items are in store" atIndex:[allItems count]];
return allItems;
}
-(abc *) createItem:(NSMutableDictionary *)dictionary
{
abc *p=[[abc alloc] init];
[p initWithDictionary:dictionary];
[allItems insertObject:p atIndex:0];
return p;
}
-(NSString *) saveItemPath
{
NSArray *documentdirectories=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString * documentDirectory=[documentdirectories objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"items.archive"];
}
-(BOOL) saveChanges
{
NSString *path=[self saveItemPath];
return [NSKeyedArchiver archiveRootObject:allItems toFile:path];
}
now you can use the global variable like this
[xyz sharedStore]allItems]
now do one more thing, add this line to application did enter background
[xyz sharedStore]saveChanges];
The best way to store the data specially if you are using web technologies to develop your mobile app is to use SQLite.You can store, retrieve your data in offline or online mode. Also another advantage is you can port your database to any mobile device or Desktop app if you decided to go native.
If you have more question or require help contact me and i can give you more info of how to use SQLite with web technologies?
If you want to save time on designing a Web GUI for your mobile app you can use Mobile web framework, Which speed up development time and allows you to access some native API's on the device. I recommend Sencha: http://www.sencha.com/products/touch/ (Really nice framework base on Html5, JS, CSS3)
You should use Core Data (which uses SQLite).
You need to look into persistence, and the best option for that is Core Data. Aside from persistence, you get a lot of other great benefits that I and others have outlined here and here, for instance. As mentioned, you can use SQLite as the backing store for the app, and then you can access the object representations of the entries however you wish. If you're dealing with syncing, you may also want to look into iCloud, which you can find information about here.

How to do database migration if SQLite is used?

My app is on App Store and in its new version I have updated the schema, like number of columns has been changed and name of columns has been changed. But now when I uploaded the new version on App Store, the app is crashing.
I came to know that this is happening because I have changed the schema. I have use SQLite so is there any solution to this problem?
SQLite supports a 'version' when you open (or create) the DB. This is a unique value that you decide. When you change the schema you should change the 'version' string so that the old database is abandoned for a new one. In the same way you would handle creating the database on first setup, you must then also manually migrate any important data from the previous database to the new one.
Unfortunately there's no way to role back an app to the last good version, the best you can do is to remove your app from the store for a few days until you get a fix into place.
Try like this.
Check the version
If new then - if you want Fetch all data from old DB
and remove the old DB. else
no changes
Create new db
Insert data what get from old DB
NSString *dbPath = [documentsDirectory stringByAppendingPathComponent:dbName];
BOOL dbSuccess = [fileManager fileExistsAtPath:dbPath];
if (dbSuccess) {
NSDictionary *oldDetailsDict = [NSDictionary dictionaryWithContentsOfFile:writablePath];
if(![[oldDetailsDict valueForKey:#"version"] isEqualToString:Current_APP_VERSION]){
NSString *oldVersion = [oldDetailsDict valueForKey:#"version"];
if ([oldVersion isEqualToString:Current_APP_VERSION]) {
//no code change needed
}else{
//Get data from old db
//Data moved to array
NSArray *oldDataArray = [Product getDataFromDatabase];
//Remove the old Database
NSString *deletePath = [documentsDirectory stringByAppendingPathComponent:#"sample.sqlite"];
[fileManager removeItemAtPath:deletePath error:nil];
//Create the new DB for new version
[self createdatabase];
//Again Insert the old data to new db
for (Product *pro in oldDataArray) {
[pro insertToDB];
}
}
}
}

Core Data fault: how to load an entities relationship set of objects correctly

For example:
I have two entities named Projectand Todo where a project has many todos (to-many relationship) and each todo has one Project(see image).
In my ViewController.h I have something like:
...
NSArray *projectArray;
NSArray *todosArray;
#property (nonatomic,retain) NSArray *projectArray;
#property (nonatomic,retain) NSArray *todosArray;
...
In my ViewController.m I have something like:
...
#synthesize projectArray,todosArray;
...
self.projectArray = [self fetchRequestForAllProjects];
...
The user has an interface where he is able to select between all different projects. As soon as the user selects a project, the related todo objects have to be set to be loaded and presented.
Question 1: How do I load the set of todos into the todosArray in the best way?
I was doing it like that (also in the ViewController.m):
...
// after deselecting a project entry I reset the todosArray
self.todosArray = nil;
...
//when the user selects a new project I reset the todosArray like this:
self.todosArray = [selectedProject.todos allObjects];
...
But somehow the app very rarely crashes in the last line of code. Is there any problem with my code?
Question 2: Would it be better to use another fetch request for the todos Objects?
UPDATE:
I am using the todosArrayin various methods of the ViewController.m:
(a) get the count of objects,
(b) present each todos entry inside a table view row, and
(c) to identify the selected todo entry (threw row selection)
Answer #1
It is best to sort them when you pull everything out of the set into an array. This will keep your user experience consistent:
NSSet *projectTodoEntities = [mySelectedProject valueForKey:#"todos"];
NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:#"myKey" ascending:YES];
NSArray *sortedToDos = [projectTodoEntities sortedArrayUsingDescriptors:[NSArray arrayWithObject:sorter]];
Answer #2
No, fetching is expensive compared to just accessing a relationship. A fetch will hit disk, etc.
For answer #1.
1). Please make sure whether selectedProject is deleted on other thread, if it is deleted, core data will mark this NSManagedObject as invalid, when you try to access property of this object, a NSObjectInaccessibleException will be thrown.
2). All NSManagedObject associates NSManagedObjectContext, the context is limited on certain thread or thread queue, when you access "todos" relationship while it is in fault state, it will trigger a fetching from persistent store, you must make sure whether execution thread is valid for NSManagedObjectContext, otherwise you should use below code.
NSManagedObjectContext *context = [selectedProject managedObjectContext];
__weak YouControllerClass *weakSelf;
[context performBlockAndWait:^{
weakSelf.todosArray = [selectedProject.todos allObjects];
}];
Answer #2: Would it be better to use an other fetch request for the todos Objects?
By default the "todos" relationship is returned as fault state, when you access project property "todos:, it actually triggers fetching from persistent store for 1st time, core data may cache these "todos" objects in memory later, so you will get fast access in future (unless you reset NSManagedObjectContext)
For most scenarios, like user checks his limit todo lists, it is ok to trigger another fetch request, the performance is not real problem if there is no huge blob data in todo object.
For performance critical scenarios, like use core data to save hundreds of photos and metadata as a relationship, when you draws all these photos on UIView based on height, width or URL property of photo object, you make consider pre-fetching photo meta to avoid performance hit (io operation).