Replace ActiveAndroid queries with raw SQLite queries

pull/145/head
Alinson S. Xavier 9 years ago
parent add08d6054
commit efc7b2cebb

@ -58,6 +58,7 @@ dependencies {
compile 'org.apmem.tools:layouts:1.10@aar'
compile 'com.opencsv:opencsv:3.7'
compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
compile 'org.jetbrains:annotations-java5:15.0'
compile 'com.jakewharton:butterknife:8.0.1'
apt 'com.jakewharton:butterknife-compiler:8.0.1'

@ -19,18 +19,18 @@
package org.isoron.uhabits;
import android.app.Application;
import android.content.Context;
import android.support.annotation.Nullable;
import android.app.*;
import android.content.*;
import android.support.annotation.*;
import com.activeandroid.ActiveAndroid;
import com.activeandroid.*;
import org.isoron.uhabits.models.HabitList;
import org.isoron.uhabits.utils.DatabaseUtils;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import java.io.File;
import java.io.*;
import javax.inject.Inject;
import javax.inject.*;
/**
* The Android application for Loop Habit Tracker.
@ -92,7 +92,7 @@ public class HabitsApplication extends Application
{
if (context != null) context
.getClassLoader()
.loadClass("org.isoron.uhabits.unit.models.HabitTest");
.loadClass("org.isoron.uhabits.BaseAndroidTest");
return true;
}
catch (final Exception e)

@ -21,13 +21,14 @@ package org.isoron.uhabits.models.sqlite;
import android.database.sqlite.*;
import android.support.annotation.*;
import android.support.annotation.Nullable;
import com.activeandroid.*;
import com.activeandroid.query.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.isoron.uhabits.utils.*;
import org.jetbrains.annotations.*;
import java.util.*;
@ -36,14 +37,23 @@ import java.util.*;
*/
public class SQLiteCheckmarkList extends CheckmarkList
{
@Nullable
private HabitRecord habitRecord;
@NonNull
private final SQLiteUtils<CheckmarkRecord> sqlite;
public SQLiteCheckmarkList(Habit habit)
{
super(habit);
sqlite = new SQLiteUtils<>(CheckmarkRecord.class);
}
@Override
public void add(List<Checkmark> checkmarks)
{
check(habit.getId());
String query =
"insert into Checkmarks(habit, timestamp, value) values (?,?,?)";
@ -69,16 +79,26 @@ public class SQLiteCheckmarkList extends CheckmarkList
}
}
@NonNull
@Override
public List<Checkmark> getByInterval(long fromTimestamp, long toTimestamp)
{
check(habit.getId());
computeAll();
List<CheckmarkRecord> records = select()
.and("timestamp >= ?", fromTimestamp)
.and("timestamp <= ?", toTimestamp)
.execute();
String query = "select habit, timestamp, value " +
"from checkmarks " +
"where habit = ? and timestamp >= ? and timestamp <= ? " +
"order by timestamp desc";
String params[] = {
Long.toString(habit.getId()),
Long.toString(fromTimestamp),
Long.toString(toTimestamp)
};
List<CheckmarkRecord> records = sqlite.query(query, params);
for (CheckmarkRecord record : records) record.habit = habitRecord;
return toCheckmarks(records);
}
@ -98,19 +118,31 @@ public class SQLiteCheckmarkList extends CheckmarkList
@Nullable
protected Checkmark getNewestComputed()
{
CheckmarkRecord record = select().limit(1).executeSingle();
check(habit.getId());
String query = "select habit, timestamp, value " +
"from checkmarks " +
"where habit = ? " +
"order by timestamp desc " +
"limit 1";
String params[] = { Long.toString(habit.getId()) };
CheckmarkRecord record = sqlite.querySingle(query, params);
if (record == null) return null;
record.habit = habitRecord;
return record.toCheckmark();
}
@NonNull
private From select()
@Contract("null -> fail")
private void check(Long id)
{
return new Select()
.from(CheckmarkRecord.class)
.where("habit = ?", habit.getId())
.and("timestamp <= ?", DateUtils.getStartOfToday())
.orderBy("timestamp desc");
if (id == null) throw new RuntimeException("habit is not saved");
if (habitRecord != null) return;
habitRecord = HabitRecord.get(id);
if (habitRecord == null) throw new RuntimeException("habit not found");
}
@NonNull

@ -19,8 +19,10 @@
package org.isoron.uhabits.models.sqlite;
import android.database.sqlite.*;
import android.support.annotation.*;
import com.activeandroid.*;
import com.activeandroid.query.*;
import org.isoron.uhabits.models.*;
@ -37,9 +39,13 @@ public class SQLiteHabitList extends HabitList
private HashMap<Long, Habit> cache;
private final SQLiteUtils<HabitRecord> sqlite;
private SQLiteHabitList()
{
cache = new HashMap<>();
sqlite = new SQLiteUtils<>(HabitRecord.class);
}
/**
@ -77,13 +83,18 @@ public class SQLiteHabitList extends HabitList
@Override
public int countActive()
{
return select().count();
SQLiteDatabase db = Cache.openDatabase();
SQLiteStatement st = db.compileStatement(
"select count(*) from habits where archived = 0");
return (int) st.simpleQueryForLong();
}
@Override
public int countWithArchived()
{
return selectWithArchived().count();
SQLiteDatabase db = Cache.openDatabase();
SQLiteStatement st = db.compileStatement("select count(*) from habits");
return (int) st.simpleQueryForLong();
}
@Override
@ -91,8 +102,17 @@ public class SQLiteHabitList extends HabitList
public List<Habit> getAll(boolean includeArchive)
{
List<HabitRecord> recordList;
if (includeArchive) recordList = selectWithArchived().execute();
else recordList = select().execute();
if (includeArchive)
{
String query = HabitRecord.SELECT + "order by position";
recordList = sqlite.query(query, null);
}
else
{
String query = HabitRecord.SELECT + "where archived = 0 " +
"order by position";
recordList = sqlite.query(query, null);
}
List<Habit> habits = new LinkedList<>();
for (HabitRecord record : recordList)
@ -127,11 +147,10 @@ public class SQLiteHabitList extends HabitList
@Nullable
public Habit getByPosition(int position)
{
HabitRecord record = selectWithArchived()
.where("position = ?", position)
.executeSingle();
if(record != null) return getById(record.getId());
String query = HabitRecord.SELECT + "where position = ? limit 1";
String params[] = { Integer.toString(position) };
HabitRecord record = sqlite.querySingle(query, params);
if (record != null) return getById(record.getId());
return null;
}
@ -222,18 +241,4 @@ public class SQLiteHabitList extends HabitList
}
}
@NonNull
private From select()
{
return new Select()
.from(HabitRecord.class)
.where("archived = 0")
.orderBy("position");
}
@NonNull
private From selectWithArchived()
{
return new Select().from(HabitRecord.class).orderBy("position");
}
}

@ -20,12 +20,13 @@
package org.isoron.uhabits.models.sqlite;
import android.support.annotation.*;
import android.support.annotation.Nullable;
import com.activeandroid.query.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.isoron.uhabits.utils.*;
import org.jetbrains.annotations.*;
import java.util.*;
@ -34,9 +35,15 @@ import java.util.*;
*/
public class SQLiteRepetitionList extends RepetitionList
{
private final SQLiteUtils<RepetitionRecord> sqlite;
@Nullable
private HabitRecord habitRecord;
public SQLiteRepetitionList(@NonNull Habit habit)
{
super(habit);
sqlite = new SQLiteUtils<>(RepetitionRecord.class);
}
/**
@ -59,25 +66,56 @@ public class SQLiteRepetitionList extends RepetitionList
@Override
public List<Repetition> getByInterval(long timeFrom, long timeTo)
{
return toRepetitions(selectFromTo(timeFrom, timeTo).execute());
check(habit.getId());
String query = "select habit, timestamp " +
"from Repetitions " +
"where habit = ? and timestamp >= ? and timestamp <= ? " +
"order by timestamp";
String params[] = {
Long.toString(habit.getId()),
Long.toString(timeFrom),
Long.toString(timeTo)
};
List<RepetitionRecord> records = sqlite.query(query, params);
return toRepetitions(records);
}
@Override
@Nullable
public Repetition getByTimestamp(long timestamp)
{
RepetitionRecord record =
select().where("timestamp = ?", timestamp).executeSingle();
check(habit.getId());
String query = "select habit, timestamp " +
"from Repetitions " +
"where habit = ? and timestamp = ? " +
"limit 1";
String params[] =
{ Long.toString(habit.getId()), Long.toString(timestamp) };
RepetitionRecord record = sqlite.querySingle(query, params);
if (record == null) return null;
record.habit = habitRecord;
return record.toRepetition();
}
@Override
public Repetition getOldest()
{
RepetitionRecord record = select().limit(1).executeSingle();
check(habit.getId());
String query = "select habit, timestamp " +
"from Repetitions " +
"where habit = ? " +
"order by timestamp asc " +
"limit 1";
String params[] = { Long.toString(habit.getId()) };
RepetitionRecord record = sqlite.querySingle(query, params);
if (record == null) return null;
record.habit = habitRecord;
return record.toRepetition();
}
@ -93,33 +131,29 @@ public class SQLiteRepetitionList extends RepetitionList
observable.notifyListeners();
}
@NonNull
private From select()
@Contract("null -> fail")
private void check(Long id)
{
return new Select()
.from(RepetitionRecord.class)
.where("habit = ?", habit.getId())
.and("timestamp <= ?", DateUtils.getStartOfToday())
.orderBy("timestamp");
}
if (id == null) throw new RuntimeException("habit is not saved");
@NonNull
private From selectFromTo(long timeFrom, long timeTo)
{
return select()
.and("timestamp >= ?", timeFrom)
.and("timestamp <= ?", timeTo);
if (habitRecord != null) return;
habitRecord = HabitRecord.get(id);
if (habitRecord == null) throw new RuntimeException("habit not found");
}
@NonNull
private List<Repetition> toRepetitions(
@Nullable List<RepetitionRecord> records)
@NonNull List<RepetitionRecord> records)
{
List<Repetition> reps = new LinkedList<>();
if (records == null) return reps;
check(habit.getId());
List<Repetition> reps = new LinkedList<>();
for (RepetitionRecord record : records)
{
record.habit = habitRecord;
reps.add(record.toRepetition());
}
return reps;
}

@ -21,12 +21,14 @@ package org.isoron.uhabits.models.sqlite;
import android.database.sqlite.*;
import android.support.annotation.*;
import android.support.annotation.Nullable;
import com.activeandroid.*;
import com.activeandroid.query.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.jetbrains.annotations.*;
import java.util.*;
@ -35,6 +37,12 @@ import java.util.*;
*/
public class SQLiteScoreList extends ScoreList
{
@Nullable
private HabitRecord habitRecord;
@NonNull
private final SQLiteUtils<ScoreRecord> sqlite;
/**
* Constructs a new ScoreList associated with the given habit.
*
@ -43,36 +51,13 @@ public class SQLiteScoreList extends ScoreList
public SQLiteScoreList(@NonNull Habit habit)
{
super(habit);
}
@Override
@NonNull
public List<Score> getAll()
{
computeAll();
List<ScoreRecord> records = select().execute();
List<Score> scores = new LinkedList<>();
for (ScoreRecord rec : records)
scores.add(rec.toScore());
return scores;
}
@Override
public void invalidateNewerThan(long timestamp)
{
new Delete()
.from(ScoreRecord.class)
.where("habit = ?", habit.getId())
.and("timestamp >= ?", timestamp)
.execute();
sqlite = new SQLiteUtils<>(ScoreRecord.class);
}
@Override
public void add(List<Score> scores)
{
check(habit.getId());
String query =
"insert into Score(habit, timestamp, score) values (?,?,?)";
@ -99,33 +84,83 @@ public class SQLiteScoreList extends ScoreList
}
}
@Override
@NonNull
public List<Score> getAll()
{
check(habit.getId());
computeAll();
String query = "select habit, timestamp, score from Score " +
"where habit = ? order by timestamp desc";
String params[] = {Long.toString(habit.getId())};
List<ScoreRecord> records = sqlite.query(query, params);
for (ScoreRecord record : records) record.habit = habitRecord;
List<Score> scores = new LinkedList<>();
for (ScoreRecord rec : records)
scores.add(rec.toScore());
return scores;
}
@Override
@Nullable
public Score getByTimestamp(long timestamp)
{
check(habit.getId());
computeAll();
ScoreRecord record =
select().where("timestamp = ?", timestamp).executeSingle();
String query = "select habit, timestamp, score from Score " +
"where habit = ? and timestamp = ? " +
"order by timestamp desc";
String params[] =
{Long.toString(habit.getId()), Long.toString(timestamp)};
ScoreRecord record = sqlite.querySingle(query, params);
if (record == null) return null;
record.habit = habitRecord;
return record.toScore();
}
@Override
public void invalidateNewerThan(long timestamp)
{
new Delete()
.from(ScoreRecord.class)
.where("habit = ?", habit.getId())
.and("timestamp >= ?", timestamp)
.execute();
}
@Nullable
@Override
protected Score getNewestComputed()
{
ScoreRecord record = select().limit(1).executeSingle();
check(habit.getId());
String query = "select habit, timestamp, score from Score " +
"where habit = ? order by timestamp desc " +
"limit 1";
String params[] = {Long.toString(habit.getId())};
ScoreRecord record = sqlite.querySingle(query, params);
if (record == null) return null;
record.habit = habitRecord;
return record.toScore();
}
private From select()
@Contract("null -> fail")
private void check(Long id)
{
return new Select()
.from(ScoreRecord.class)
.where("habit = ?", habit.getId())
.orderBy("timestamp desc");
if (id == null) throw new RuntimeException("habit is not saved");
if(habitRecord != null) return;
habitRecord = HabitRecord.get(id);
if (habitRecord == null) throw new RuntimeException("habit not found");
}
}

@ -0,0 +1,84 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.models.sqlite;
import android.database.*;
import android.database.sqlite.*;
import android.support.annotation.*;
import com.activeandroid.*;
import org.isoron.uhabits.models.sqlite.records.*;
import java.util.*;
public class SQLiteUtils<T extends SQLiteRecord>
{
private Class klass;
public SQLiteUtils(Class klass)
{
this.klass = klass;
}
@NonNull
public List<T> query(String query, String params[])
{
SQLiteDatabase db = Cache.openDatabase();
try (Cursor c = db.rawQuery(query, params))
{
return cursorToMultipleRecords(c);
}
}
@Nullable
public T querySingle(String query, String params[])
{
SQLiteDatabase db = Cache.openDatabase();
try(Cursor c = db.rawQuery(query, params))
{
if (!c.moveToNext()) return null;
return cursorToSingleRecord(c);
}
}
@NonNull
private List<T> cursorToMultipleRecords(Cursor c)
{
List<T> records = new LinkedList<>();
while (c.moveToNext()) records.add(cursorToSingleRecord(c));
return records;
}
@NonNull
private T cursorToSingleRecord(Cursor c)
{
try
{
T record = (T) klass.newInstance();
record.copyFrom(c);
return record;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
}

@ -19,6 +19,8 @@
package org.isoron.uhabits.models.sqlite.records;
import android.database.*;
import com.activeandroid.*;
import com.activeandroid.annotation.*;
@ -29,7 +31,7 @@ import org.isoron.uhabits.models.sqlite.*;
* The SQLite database record corresponding to a {@link Checkmark}.
*/
@Table(name = "Checkmarks")
public class CheckmarkRecord extends Model
public class CheckmarkRecord extends Model implements SQLiteRecord
{
/**
* The habit to which this checkmark belongs.
@ -52,6 +54,13 @@ public class CheckmarkRecord extends Model
@Column(name = "value")
public Integer value;
@Override
public void copyFrom(Cursor c)
{
timestamp = c.getLong(1);
value = c.getInt(2);
}
public Checkmark toCheckmark()
{
SQLiteHabitList habitList = SQLiteHabitList.getInstance();

@ -20,6 +20,7 @@
package org.isoron.uhabits.models.sqlite.records;
import android.annotation.*;
import android.database.*;
import android.support.annotation.*;
import com.activeandroid.*;
@ -28,17 +29,24 @@ import com.activeandroid.query.*;
import com.activeandroid.util.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.utils.DatabaseUtils;
import java.lang.reflect.*;
/**
* The SQLite database record corresponding to a {@link Habit}.
*/
@Table(name = "Habits")
public class HabitRecord extends Model
public class HabitRecord extends Model implements SQLiteRecord
{
public static final String HABIT_URI_FORMAT =
"content://org.isoron.uhabits/habit/%d";
public static String SELECT =
"select id, color, description, freq_den, freq_num, " +
"name, position, reminder_hour, reminder_min, " +
"highlight, archived, reminder_days from habits ";
@Column(name = "name")
public String name;
@ -147,6 +155,23 @@ public class HabitRecord extends Model
this.archived = model.getArchived();
}
@Override
public void copyFrom(Cursor c)
{
setId(c.getLong(0));
color = c.getInt(1);
description = c.getString(2);
freqDen = c.getInt(3);
freqNum = c.getInt(4);
name = c.getString(5);
position = c.getInt(6);
reminderHour = c.getInt(7);
reminderMin = c.getInt(8);
highlight = c.getInt(9);
archived = c.getInt(10);
reminderDays = c.getInt(11);
}
public void copyTo(Habit habit)
{
habit.setName(this.name);
@ -172,4 +197,20 @@ public class HabitRecord extends Model
save();
updateId(getId(), id);
}
private void setId(Long id)
{
// HACK: The id field is declared private by ActiveAndroid and
// there are no setters. (WTF?)
try
{
Field f = (Model.class).getDeclaredField("mId");
f.setAccessible(true);
f.set(this, id);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
}

@ -19,6 +19,8 @@
package org.isoron.uhabits.models.sqlite.records;
import android.database.*;
import com.activeandroid.*;
import com.activeandroid.annotation.*;
@ -29,7 +31,7 @@ import org.isoron.uhabits.models.sqlite.*;
* The SQLite database record corresponding to a {@link Repetition}.
*/
@Table(name = "Repetitions")
public class RepetitionRecord extends Model
public class RepetitionRecord extends Model implements SQLiteRecord
{
@Column(name = "habit")
public HabitRecord habit;
@ -37,15 +39,21 @@ public class RepetitionRecord extends Model
@Column(name = "timestamp")
public Long timestamp;
public static RepetitionRecord get(Long id)
{
return RepetitionRecord.load(RepetitionRecord.class, id);
}
public void copyFrom(Repetition repetition)
{
habit = HabitRecord.get(repetition.getHabit().getId());
timestamp = repetition.getTimestamp();
}
public static RepetitionRecord get(Long id)
@Override
public void copyFrom(Cursor c)
{
return RepetitionRecord.load(RepetitionRecord.class, id);
timestamp = c.getLong(1);
}
public Repetition toRepetition()

@ -0,0 +1,27 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.models.sqlite.records;
import android.database.*;
public interface SQLiteRecord
{
void copyFrom(Cursor c);
}

@ -19,6 +19,8 @@
package org.isoron.uhabits.models.sqlite.records;
import android.database.*;
import com.activeandroid.*;
import com.activeandroid.annotation.*;
@ -29,11 +31,8 @@ import org.isoron.uhabits.models.sqlite.*;
* The SQLite database record corresponding to a Score.
*/
@Table(name = "Score")
public class ScoreRecord extends Model
public class ScoreRecord extends Model implements SQLiteRecord
{
/**
* Habit to which this score belongs to.
*/
@Column(name = "habit")
public HabitRecord habit;
@ -50,6 +49,13 @@ public class ScoreRecord extends Model
@Column(name = "score")
public Integer score;
@Override
public void copyFrom(Cursor c)
{
timestamp = c.getLong(1);
score = c.getInt(2);
}
/**
* Constructs and returns a {@link Score} based on this record's data.
*

@ -19,26 +19,20 @@
package org.isoron.uhabits.ui.habits.list;
import android.support.annotation.NonNull;
import org.isoron.uhabits.HabitsApplication;
import org.isoron.uhabits.R;
import org.isoron.uhabits.commands.CommandRunner;
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.HabitList;
import org.isoron.uhabits.tasks.ExportCSVTask;
import org.isoron.uhabits.tasks.ExportDBTask;
import org.isoron.uhabits.tasks.ImportDataTask;
import org.isoron.uhabits.ui.BaseSystem;
import org.isoron.uhabits.ui.habits.list.controllers.HabitCardListController;
import org.isoron.uhabits.utils.DateUtils;
import org.isoron.uhabits.utils.Preferences;
import java.io.File;
import java.io.IOException;
import javax.inject.Inject;
import android.os.*;
import android.support.annotation.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.ui.*;
import org.isoron.uhabits.ui.habits.list.controllers.*;
import org.isoron.uhabits.utils.*;
import java.io.*;
import javax.inject.*;
public class ListHabitsController
implements ImportDataTask.Listener, HabitCardListController.HabitListener
@ -168,8 +162,10 @@ public class ListHabitsController
prefs.updateLastAppVersion();
if (prefs.isFirstRun()) onFirstRun();
new Handler().postDelayed(() -> {
system.updateWidgets();
system.scheduleReminders();
}, 1000);
}
@Override

Loading…
Cancel
Save