Structuring an SQLite database to separate readable/writeabale data - android

I'm writing an app that will allow users to read short stories that are stored in an SQLite database.
So far so good.
But now I want to add features that involve writing to the database (saving the Y location of a ScrollView so the user can pick up where they left off, bookmarking stories, etc).
Should I add these values to the books table, or should I create a separate table user_settings with columns like id (int), story_id (int), y_position (int), bookmarked (boolean)?
Note: I'm also thinking ahead to the possibility of storing stories on a non-local database in the future.
My other question is: do I need to move the database somewhere to be able to write to it? I'm using SQLiteAssetHelper and the database is currently at /assets/databases/database.db. I'm hearing some talk of a /data/data/mypackage folder but I can't see it in my project.
My database setup is currently as follows:
authors
id
name
name_alphanumeric
books
id
title
author_id
collection
body
If it's useful, here's my DatabaseHelper so far:
public class DatabaseHelper extends SQLiteAssetHelper {
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "database9.db";
private static final String BOOKS = "books";
private static final String AUTHORS = "authors";
public DatabaseHelper (Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
// setForcedUpgrade();
}
// Getting all books
public ArrayList<Author> getAllAuthors() {
ArrayList<Author> authorList = new ArrayList<>();
// Select all query
String selectQuery = "SELECT id, name FROM " + AUTHORS + " ORDER BY name_alphabetic";
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
// create new author object
Author author = new Author();
// set ID and name of author object
author.setID(Integer.parseInt(cursor.getString(0)));
author.setName(cursor.getString(1));
// pass author object to authorList array
authorList.add(author);
} while (cursor.moveToNext());
}
// return author list
return authorList;
}
// Getting all stories
public List<Book> getAllStories(int authorID) {
List<Book> storyList = new ArrayList<>();
// Select all query
String selectQuery = "SELECT id, title FROM " + BOOKS + " WHERE author_id = " + authorID;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
Book book = new Book();
book.setStoryID(Integer.parseInt(cursor.getString(0)));
book.setTitle(cursor.getString(1));
storyList.add(book);
} while (cursor.moveToNext());
}
// return contact list
return storyList;
}
// Get all collections
public List<Book> getAllCollections(int authorID) {
List<Book> collectionsList = new ArrayList<>();
// Select all query
String selectQuery = "SELECT DISTINCT collection FROM " + BOOKS + " WHERE author_id = " + authorID;
Log.i("stories", selectQuery);
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
Book book = new Book();
book.setCollection(cursor.getString(0));
// Log.i("stories", cursor.getString(0));
collectionsList.add(book);
} while (cursor.moveToNext());
}
return collectionsList;
// not sure how to log collectionsList here
}
// Get story
public String getStoryBody(int storyID) {
// Log.i("stories", Integer.toString(storyID));
String storyBody = "";
// String storyBody();
// Select all query
String selectQuery = "SELECT body FROM " + BOOKS + " WHERE id = " + storyID;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
storyBody = cursor.getString(0);
} while (cursor.moveToNext());
}
return storyBody;
}
public int setScrollPosition(int scrollY, int storyID) {
String insertQuery = "UPDATE " + BOOKS + " SET scroll_position = " + scrollY + " WHERE id = " + storyID;
Log.i("insert", insertQuery);
SQLiteDatabase db = this.getWritableDatabase();
db.execSQL(insertQuery);
return 0;
}
public int getScrollPosition(int storyID) {
int scrollPosition = 0;
String selectQuery = "SELECT scroll_position FROM " + BOOKS + " WHERE id = " + storyID;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
scrollPosition = cursor.getInt(0);
} while (cursor.moveToNext());
}
return scrollPosition;
}
}

But now I want to add features that involve writing to the database
(saving the Y location of a ScrollView so the user can pick up where
they left off, bookmarking stories, etc).
Should I add these values to the books table, or should I create a
separate table user_settings with columns like id (int), story_id
(int), y_position (int), bookmarked (boolean)?
I think you have made it clear that they are USER values, so it is very likely that a separate user table would be the better more manageable solution.
My other question is: do I need to move the database somewhere to be
able to write to it? I'm using SQLiteAssetHelper and the database is
currently at /assets/databases/database.db. I'm hearing some talk of a
/data/data/mypackage folder but I can't see it in my project.
In all likeliehood the database has been copied from the assets folder into data/data/yourpackage/databases/dbfilename by SQLiteAssetHelper (as I understand that's primarily what it's for. However I've never used it.) Such folders have limited access (normally only the Application (rooted device an exception)) so that could well be why you can't see it.
As such there is likely nothing required in the way of permissions for writing to/updating the database.

Related

How to customize Room Database Query results that we can achieve in normal SQLite Open helper class

In Room database we write queries as DAO to get results from database. In normal SQLite OpenHelper class we can customize the query results as per our need.
public ArrayList<MyData> getMyData(String id) {
ArrayList<MyData> entries=new ArrayList<>();
SQLiteDatabase db = this.getWritableDatabase();
String query = "SELECT * FROM " + MYDATA + " WHERE " + ID + " = ?";
Cursor c1 = db.rawQuery(query, new String[]{id});
List<String> business=new ArrayList<>();
if(c2.moveToFirst()){
MyData entry=new MyData();
entry.setId(c2.getString(0));
String dataValue=c2.getString(1);
String val="";
if(dataValue.contains("__")){
String[] valueSplit =dataValue.split("__");
if(valueSplit[0]!=null){
val=address[0];
}
}else{
val="";
}
entries.add(entry);
}
return entries;
}
In this query, results obtained from Database is customized based on some condition, added in ArrayList MyData object and then final result is returned. How it can be done in Room Database
If i understand your question correctly you can use #query in your DAO like this
#Query("SELECT * FROM MYDATA WHERE id = :id")
public ArrayList<MyData> getMyData(String id);
Then in your database view model expose getMyData(String id) and process your data

How do I view data from database table according to a user condition?

I would to like to display data from a local database table according to a user condition.
Code to display:
public void viewContact(){
String name = getIntent().getStringExtra("name").toString();
tvName.setText(name);
String phone = db.getContacts(name).toString();
tvPhone.setText(phone);
String web = db.getContacts(name.toString();
tvWeb.setText(web);
}
DBHelper.class:
public Cursor getContacts(String therapist_name){
String selectQuery = " SELECT therapist_phone, therapist_web " +
" FROM " + THERAPIST_TABLE + " WHERE " + THERA_NAME + " = " + "'" + therapist_name + "'";
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
return cursor;
}
Can anyone point out where I've gone wrong?
Explanation:
The activity receives contact name from previous activity (hence getIntent()). Then with that, it would like to view data from database relating to the contact and thus would like to view the phone_number and website columns.
So if therapist_name is equal that of selected contact from previous activity, it would only display the therapist_number and therapist_website of that contact in the next activity.
You should have an Object Contact with the properties you want, and everytime you run the query, go trough the steps Ana said of checking if the cursor is null and moving to first (i personally check the size of the cursor also, to Log an error if is empty).
And then return the created object, so when you call the method, you have everything there, something like this:
public List<Contact> getContacts(String therapist_name){
List<Contact> listOfContacts = new ArrayList<>();
String selectQuery = " SELECT therapist_phone, therapist_web " +
" FROM " + THERAPIST_TABLE + " WHERE " + THERA_NAME + " = " + "'" + therapist_name + "'";
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
//Save data in variables here
String ph_no = cursor.getString(0); // therapist contact
String web = cursor.getString(1); //therapist web
listOfContacts.add(new Contact(ph_no, web));
} while (cursor.moveToNext());
}
}
return listOfContacts;
}
This returns a list of objects, just in case there is more than one match. You can tweak it to get one, or the first match, and return just the Contact object, so you would have your data,
You are just returning cursor object. Read the data and then return the retrieved data.
Example:
Cursor cursor = db.rawQuery(selectQuery, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
//Save data in variables here
ph_no = cursor.getString(0); // therapist contact
web = cursor.getString(1); //therapist web
} while (cursor.moveToNext());
}
}
//pass an array of ph_no and web from here now

How to correctly write big CRUD sqlite android app

I'm writing an app that manipulates with database consists of 3 tables. I created this database from json file using models (Worker model, specialty model) with getters and setters. Now I want to get specific info from this database. I'v already made it but my code is pretty silly. What I need is to change the architecture of my app but I don't know how exactly it should looks like.
This is the examples of my methods, and they are pretty week
This is how I add info into database. I like it, I think it's correct:
public void addWorker(Worker worker){
List<Specialty> specialty;
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
//fixind names
String f_name = fixName(worker.getF_name());
String l_name = fixName(worker.getL_name());
String birthday = fixDate(worker.getBirthday());
values.put(KEY_F_NAME, f_name);
values.put(KEY_L_NAME, l_name);
values.put(KEY_BIRTHDAY, birthday);
values.put(KEY_AVATR_URL, worker.getAvart_url());
specialty = worker.getSpecialty();
long worker_id = db.insert(TABLE_WORKERS,null,values);
//add unique specialty
for (Specialty spec: specialty){
createRelations(worker_id, spec.getSpecialty_id());
if (getCount(spec.getSpecialty_id()) == 0){
addSpecialty(spec);
}
}
}
And this is how I take info from database:
public String[] getFullInfo(String worker_name){
String selectQuery = "HUGE QUERY HERE "where workers.f_name =?";
Log.e(LOG_TAG, selectQuery);
SQLiteDatabase db = this.getReadableDatabase();
Cursor c = db.rawQuery(selectQuery, new String [] {worker_name});
String[] details = new String[5];
c.moveToFirst();
while (c.isAfterLast() == false){
details[0] = c.getString(c.getColumnIndex(KEY_F_NAME));
details[1] = c.getString(c.getColumnIndex(KEY_L_NAME));
details[2] = c.getString(c.getColumnIndex(KEY_BIRTHDAY));
details[3] = c.getString(c.getColumnIndex("age"));
details[4] = c.getString(c.getColumnIndex(KEY_SPEC_NAME));
c.moveToNext();
}
return details;
}
This is my another query and it has different return type:
public List<Map<String ,String>> getWorkerListBySpec(String spec_name){
String selectQuery = "HUGE QUERY HERE
"where specialty.spec_name=?";
Log.e(LOG_TAG, selectQuery);
SQLiteDatabase db = this.getReadableDatabase();
Cursor c = db.rawQuery(selectQuery, new String [] {spec_name});
//making list of workers
List<Map<String ,String>> data = new ArrayList<Map<String,String>>();
c.moveToFirst();
while (c.isAfterLast() == false){
Map<String,String> datum = new HashMap<String,String>(2);
datum.put(KEY_F_NAME, c.getString(c.getColumnIndex(KEY_F_NAME)));
datum.put(KEY_L_NAME, c.getString(c.getColumnIndex(KEY_L_NAME)));
datum.put(KEY_BIRTHDAY, c.getString(c.getColumnIndex(KEY_BIRTHDAY)));
data.add(datum);
c.moveToNext();
}
return data;
}
and the third one, which also have different return type:
public List<String> getAllSpecs_Names(){
List<String> spec_names = new ArrayList<String>();
String selectQuery = "select * from " + TABLE_SPECIALTY;
Log.e(LOG_TAG, selectQuery);
SQLiteDatabase db = this.getReadableDatabase();
Cursor c = db.rawQuery(selectQuery, null);
if (c.moveToFirst()){
do{
spec_names.add(c.getString(c.getColumnIndex(KEY_SPEC_NAME)));
}while (c.moveToNext());
}
return spec_names;
}
I know, that this is all wrong.
Please tell me how I should make all my queries.
It will be good if you give me the link to check how the app should look like
instead of returning a List or Map, you should better return a own CursorWrapper
so you could could look like this:
public WorkerCursor getWorkerListBySpec(String spec_name){
String selectQuery = "HUGE QUERY HERE
"where specialty.spec_name=?";
Log.e(LOG_TAG, selectQuery);
SQLiteDatabase db = this.getReadableDatabase();
Cursor c = db.rawQuery(selectQuery, new String [] {spec_name});
return new WorkerCursor(cursor);
}
public static class WorkerCursor extends CursorWrapper {
/**
* Creates a cursor wrapper.
*
* #param cursor The underlying cursor to wrap.
*/
public WorkerCursor(Cursor cursor) {
super(cursor);
}
public Worker getWorker() {
return getWorkerAtCursor();
}
public Worker getWorker(int position) {
if (moveToPosition(position)) {
return getWorkerCursor();
} else {
return null;
}
}
private Worker getWorkerAtCursor() {
if (isBeforeFirst() || isAfterLast()) {
return null;
}
worker worker = new Worker();
worker.name = c.getString(c.getColumnIndex(KEY_F_NAME));
....
return worker;
}
}
and the same for your List, just with a other CursorWrapper
and don't forget to close the CursorWrapper, when it's no more needed, like in onDestroy of Activity and so

Deleting data from SQLite Database is not working

I am working with SQLite and I am having trouble deleting data.
First and foremost, this is how I add data to the database:
public void addRecipe (QueryVars Recipee){
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_Recipe, Recipee.getRecipe());
db.insert(TABLE_Recipes, null, values);
db.close();
}
And this is how I get data from the database:
public List<QueryVars> getAllBooks() {
List<QueryVars> recipes = new LinkedList<QueryVars>();
String query = "SELECT * FROM " + TABLE_Recipes;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(query, null);
QueryVars Recipe = null;
if (cursor.moveToFirst()) {
do {
Recipe = new QueryVars();
// Recipe.setId(Integer.parseInt(cursor.getString(0)));
Recipe.setRecipe(cursor.getString(1));
recipes.add(Recipe);
} while (cursor.moveToNext());
}
return recipes;
}
Saving and querying for data is working perfectly fine, but when I try to delete rows with the following code it just doesn't work.
public void deleteRecipes(QueryVars Recipe) {
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_Recipes, KEY_ID + " = ?", new String[] { String.valueOf(Recipe.getId()) });
db.close();
}
This is the query I use to create the table:
private static final String CREATE_BOOK_TABLE =
"CREATE TABLE Recipes ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "Recipe TEXT"
+ ")";
And the constants I use in my code above are defined like this:
private static final TABLE_Recipes = "Recipes";
private static final String KEY_ID = "id";
private static final String KEY_Recipe = "Recipe";
private static final String[] COLUMNS = {KEY_ID,KEY_Recipe};
There are two issues in your code which could potentially be the source of the error, but both have the same cause: You are letting SQLite generate the ids of your Recipe objects but you are never setting that id to your objects.
When you add something to the database the add() method returns the id which was generated for that row. You can just set this id to the Recipe object otherwise that Recipe object won't have an id until you reload it from the database.
public void addRecipe (QueryVars Recipee){
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_Recipe, Recipee.getRecipe());
final long id = db.insert(TABLE_Recipes, null, values);
Recipee.setId(id);
db.close();
}
When you are reading the Recipe objects from the database you are not setting the id value on the object, so no Recipe object you read from the database has an id which means you cannot delete them from the database. The fix is again pretty simple:
public List<QueryVars> getAllBooks() {
List<QueryVars> recipes = new LinkedList<QueryVars>();
String query = "SELECT * FROM " + TABLE_Recipes;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(query, null);
final int idIndex = cursor.getColumnIndex(KEY_ID);
final int recipeIndex = cursor.getColumnIndex(KEY_Recipe);
QueryVars Recipe = null;
if (cursor.moveToFirst()) {
do {
Recipe = new QueryVars();
Recipe.setId(cursor.getLong(idIndex));
Recipe.setRecipe(cursor.getString(recipeIndex));
recipes.add(Recipe);
} while (cursor.moveToNext());
}
return recipes;
}
This uses getColumnIndex() to reliably get the correct index of each column and then reads the id and the recipe from the cursor and sets them to the Recipe object.
Please note that your Recipe object needs to have a long id! int ids are not compatible with the SQLiteDatabase!
You don't capture the database-generated id of your recipes and the id is zero. It doesn't match any rows in the delete.
Uncomment the
// Recipe.setId(Integer.parseInt(cursor.getString(0)));
(consider using cursor.getInt() instead)
Possibly also store the return value of insert() as the recipe id.

Android SQLite: Spinner + select sql methods

I have a Spinner which is showing SQLite data. For that I am using this select method:
public List<String> getAllProductsName(int id)
{
String buildSQL = "SELECT nome FROM " + DatabaseHelper.Produtos.TABELA + " WHERE id =" + id;
List<String> nomes = new ArrayList<String>();
SQLiteDatabase db = this.getDatabase();
Cursor cursor = database.rawQuery(buildSQL, null);
if (cursor.moveToFirst()) {
do {
nomes.add(cursor.getString(0));
} while (cursor.moveToNext());
}
return nomes;
}
The thing is, I am getting only the names but I need the ID as well. I know i could use "SELECT nome, _id FROM ", but how would I return that? Could i possibly return 2 lists (one with IDS and the other one with the Names) in the same method?
Or maybe I should create a new method that show the Names only (when i give the ID as a parameter)? Please help! thanks in advance! :)
How about something like this ... using and returning HashMap that contains ID as keys and nome as values
public HashMap<Integer,String> getAllProductsName(int id)
{
String buildSQL = "SELECT nome,_id FROM " + DatabaseHelper.Produtos.TABELA + " WHERE id =" + id;
HashMap<Integer,String> idAndNomes = new HashMap<Integer,String>();
SQLiteDatabase db = this.getDatabase();
Cursor cursor = database.rawQuery(buildSQL, null);
if (cursor.moveToFirst()) {
do {
idAndNomes.put(cursor.getInt(1), cursor.getString(0)));
} while (cursor.moveToNext());
}
return idAndNomes;
}
Then you can use:
idAndNomes.keySet() - Returns a set of the keys contained in this map. In our case ID.
idAndNomes.values() - Returns a collection of the values contained in this map. In our case nomes.

Categories

Resources