C/C++ Guides

Home

Using Data Stores

There are several ways to save or read data from a mobile device. Reading and writing to the Internet can be done with Connection classes, but can be labourious without specific helper classes. The simplest way of saving your data is with a MoSync store. This tutorial looks at creating stores, writing to them and reading them.

Creating Stores

The simplest way of saving your data is with a MoSync store. Stores are saved on the device, and are supported on all MoSync-supported platforms. A store is a single file that can easily be  read from and written to. On Symbian S60 platforms, stores can be shared between different applications; on other platforms stores are private and separate.

To create a store, use the function maOpenStore(). This function takes two parameters, a const char* with the name of the store, and an int representing the options for the store. At the moment, there is only one option, you can choose to use it or not. This is defined as MAS_CREATE_IF_NECESSARY and it will create a new store if it doesn't exist. 

The function maOpenStore() returns an MAHandle, a reference to the store that you can use with other functions.

MAHandle myStore = maOpenStore("MyStore", MAS_CREATE_IF_NECESSARY);

If the store doesn't exist on the phone, it will be created and an MAHandle will be returned. If it does exist, it will just return the handle. 

If the file cannot be created (for example, if there is no room on the device or there is a fault on the device) the value returned will match one of the STERR error values.

When you run your application in the MoRE emulator, you'll find that the stores are created in a folder called /stores in your output folder. Stores cannot currently be deployed with your application, and they have to be created at runtime.

Checking that a Store Exists

If you want to find out whether or not a store exists, call maOpenStore() with the flags set to 0. The value STERR_NONEXISTENT is returned if the store doesn't already exist.

MAHandle testStore = maOpenStore("MyStore", 0);

if(myStore == STERR_NONEXISTENT)
{
    // Store doesn't exist.
} 
else
{
     // Store does exist, and testStore is a valid handle to it.
}

Writing to Stores

Stores don't have a sophisticated system for accessing and writing. They simply provide a method for saving application data to the device. Each time a store is written, it is overwritten. You cannot append to existing stores or edit the store directly. A store is written to from a data resource. This isn't resizable, but you can edit the contents, and you can write variables into it and read their values out again. 

You can create new data resources at runtime, but you need to define an MAHandle for it first. You also need to know how much data you need to store. To create a new data resource, you use the command maCreateData(). This returns either RES_OK if there is enough memory to create it or RES_OUT_OF_MEMORY if not. You need to pass the MAHandle to it as a parameter, along with the size. You can create a new, empty MAHandle with the function maCreatePlaceholder().

String password = "p45sw0rd";
MAHandle myData = maCreatePlaceholder();
if(maCreateData(myData, password.length()) == RES_OK)
{
}

You can now write data using the method maWriteData().

String password = "p45sw0rd";
MAHandle myData = maCreatePlaceholder();
if(maCreateData(myData, password.length()) == RES_OK)
{
    maWriteData(myData, password.c_str(), 0, password.length();
}

The maWriteData() function needs the MAHandle of the data resource, a pointer to the object you want to write, the offset from the beginning of the data resource and the length of the data you wish to write. This means that you can write the variable to where ever you want in the data resource. If you are writing several pieces of data, you'll either need to keep track of their relative positions in the data, or user DataHandler as described below to do it for you.

To write the data resource to the store, you use the method maWriteStore(). This takes the MAHandle of the store, and the MAHandle of the data. 

int result = maWriteStore(myStore, myData);

Anything which was in the store previously is overwritten. If maWriteStore() returns > 0, then it has been written correctly. Other values should map to STERR. The one to watch out for here is STERR_FULL which means that there is no more storage left.

int result = maWriteStore(myStore, myData);

switch(result)
{
    case > 0:
        // Everything is fine, and the data is saved.
        break;    
    case STERR_FULL:

        // Failed, not enough space to save the data.
        break;
    case STERR_NONEXISTENT:

        // Failed, the store doesn't exist!
        break;
    case STERR_GENERIC:

        // Unknown error, possibly a device fault.
        break;
}

A Note on Store Security

As a word of warning, although our example shows a password being written to a store you should be aware that this data is not completely private. Different systems provide different security measures. On a J2ME device, such data is fairly safe. A hacker would have to know a lot about the data to be able to access it. On S60 devices however, the user can freely browse the stores you've written using their file manager. Plain text stores can be opened up and read, and images you've written to the store can be displayed. If there is any sensitivity to the data you are writing, then it is strongly suggested that you encrypt it before you write it to the store.

Closing a Store

When you've finished writing, you also need to close the store. This is done with the function maCloseStore().

maCloseStore(myStore, 0);

The second parameter (0 in this example), indicates whether or not the store should be deleted when you close it. If it is 0, you want to keep it; if you provide any other int value the store will be deleted. This is how stores are deleted, there is no other explicit function to delete stores. 

void deleteStore(const char* storeName)
{
    MAHandle store = maOpenStore(storeName, 0);
    if(store != STERR_NONEXISTENT)
    {
        // Delete the store.
        maCloseStore(store, 1); 
    }
}

Reading from Stores

Just as you write to stores from data resources, you read from them in the same way. The function maReadStore() copies the data in a store to a data resource, indicated by an MAHandle.

MAHandle myData = maCreatePlaceholder();
MAHandle myStore = maOpenStore("MyStore", 0);
if(myStore != STERR_NONEXISTENT)
{
    // The store exists, so we can read from it.
    int result = maReadStore(myStore, myData);
    if(result == RES_OUT_OF_MEMORY)
    {
        // This store is too large to read into memory - error
    }
}

Once the store has been read into the data resource, the values can be read out of it. Of course you need to know how much data to read: you can get the size of the data resource with the function maGetDataSize(), and then read the data using the method maReadData().

char password[maGetDataSize(myData)]; 
maReadData(myData, &password, 0, maGetDataSize(myData));

To use maReadData, you need to know how many bytes to read from the data resource. In the example above, the password is the only data being stored, so we can easily read out all of the data. 

If the store contains both a username and a password, then it has to be a bit more sophisticated. You are probably not using fixed lengths for strings, so you need to store some extra data about how long each string is. If you put a byte containing the length of the string (or an int if it is a lot) before the actual string data, then you can read it back more easily. These are called Pascal strings or P-Strings, and you can also use them in MoSync resource files. To read out a P-string, you need to read the first byte.

// Read a p-string.
byte len = 0;

// You now need to track your position in the data.
int position = 0; 

// Read the length.
maReadData(myData, &len, position, 1);// Read 1 byte from position

// Move onto the next byte.
position++;

// Read the string.
char password[len + 1];  //String may not be null-terminated.
maReadData(myData, &password, position, len);

// Add a null-terminator.
password[len+1] = '/0';

// Move the position on, so we're ready for the next read.
position += len;

So, if you are reading more than one item out of the data, then you need to keep track of your place in the data.

Once you've finished with the data, you can release it from memory.

maDestroyObject(myData);

This deletes the data resource from memory, but does not destroy the store.

Using DataHandler

If you want to write several different values to the data resource, and then to the store, then there is a utility called DataHandler. This can help you write several values to the resource by tracking the offsets for you. You need to include the header file MAUtil/DataHandler.h

#include "MAUtil/DataHandler.h"
using namespace MAUtil;

...

String username = "m0sync";
String password = "p45sw0rd";

// Save the values to a data resource.
MAHandle myData = maCreatePlaceholder();

// The size of the data we need, including four bytes for 
// the length of each string.
int size = username.length() + 4 + password.length() + 4; 

if(maCreateData(myData, size) == RES_OK)
{
    DataHandler* handler = new DataHandler(myData);

    int usernameLength =  username.length();
    handler->write(&usernameLength, 4);
    handler->write(username.c_str(), usernameLength);

    int passwordLength = password.length();  
    handler->write(&passwordLength, 4);
    handler->write(password.c_str(), passwordLength);
}

int result = maWriteStore(myStore, myData);

if(result > 0)
{
    // Written successfully.
    maCloseStore(myStore, 0);
}
else
{
    // Failed, delete the store.
    maCloseStore(myStore, 1);
}

As you can see from the above example, the DataHandler class takes away some of the complexity of reading and writing to stores, treating them more like a Java StreamReader or StreamWriter class.  Unlike the example above where we were reading data out of the resource and having to keep track of the position, the DataHandler will manage it for us, and allow serial access.

MoSync SDK 3.3
Copyright © 2013 MoSync AB
www.mosync.com