SQliteOpenHelper Database indexoutofbounds when called from fragment's OnResume - android

I have a problem with SQliteDatabase/OpenHelper/AssetHelper that is difficult to fix because I cannot replicate the error that many of my app users are having on my devices or on any android studio emulated devices, so this code I'm about to show you works for me, but not some others and I don't know why.
My users sometimes get an indexoutofboundsexception (I believe) when my app takes a value from a List that is populated from a database (in the assets folder). So it seems the database is not loading properly. The problem might be related to the context I'm passing when I get an instance of the DatabaseAccess class DBProgressAccess in OnResume lifecycle callback of the Fragment.
I haven't tried much to fix the issue only because the crash so rarely occurs (just once on my android 6.0 phone), but I'm getting reports from google of frequent crashing for other android devices (up to and including android 6.0).
The stack trace I have for the crashes comes from google developers console.
I have a fragment that loads at the startup of the app. In its showSets12() method called from onResume() (where I see the indexoutofbounds error) I access a database of the users scores:
private void showSets12()
{
DBProgressAccess dbProgressAccess = DBProgressAccess.getInstance(getActivity());
dbProgressAccess.openUserProgressDb();
dbProgressAccess.getDayValues();
int scoreToday = dbProgressAccess.getScores().get(0); <--I think the error comes from this
...
dbProgressAccess.closeUserProgressDb();
}
In the public DBProgressAccess class I have:
public class DBProgressAccess {
private SQLiteOpenHelper openHelper;
private SQLiteDatabase database;
private static DBProgressAccess instance;
private List<String> dates;
private List<Integer> scores;
private List<String> times;
public static final String TABLE_NAME = "UserProgress";
public List<Integer> getScores() {return scores;}
private DBProgressAccess(Context context) {
this.openHelper = new DBProgressHandler(context);
}
public static DBProgressAccess getInstance(Context context)
{
Log.d(TAG, "getInstance");
if(instance == null) {
instance = new DBProgressAccess(context);
}
return instance;
}
/**
* Open the database connection.
*/
public void openUserProgressDb() {
Log.d(TAG, "openUserProgress");
this.database = openHelper.getWritableDatabase();
}
/**
* Is todays date included in the database? If not, create it and search the database to populate lists for times and scores
*/
public void getDayValues()
{
if (checkIfDateExists()) {
Cursor cursor = searchDayProgress();
searchDatabase(cursor);
}
else
{
createDayEntry();
Cursor cursor = searchDayProgress();
searchDatabase(cursor);
}
}
/**
* Check to see if there is a record in the database for today
*/
public boolean checkIfDateExists()
{
Cursor cursor = searchDayProgress();
if(cursor.getCount() <= 0)
{
cursor.close();
return false;
}
cursor.close();
return true;
}
/**
* Insert todays date into the database
*/
private void createDayEntry()
{
Date today = Calendar.getInstance().getTime();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
String todayString = formatter.format(today);
int score = 0;
String time = "0:00:0";
ContentValues cv = new ContentValues();
cv.put("date", todayString);
cv.put("score", score);
cv.put("time", time);
database.insert(TABLE_NAME, null, cv);
}
/**
* Find the database record for today
*/
private Cursor searchDayProgress() {
String whereQuery = "SELECT * FROM UserProgress WHERE date = date('now')";
Log.d(TAG, "searchWeekProgress " + whereQuery);
return database.rawQuery(whereQuery, null);
}
/**
* Populate lists of time, date and progress taken from the database
*/
public void searchDatabase(Cursor cursor)
{
List<String> listDate = new ArrayList<>();
List<Integer> listUserProgress = new ArrayList<>();
List<String> listTime = new ArrayList<>();
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
listDate.add(cursor.getString(cursor.getColumnIndex("date")));
listUserProgress.add(cursor.getInt(cursor.getColumnIndex("score")));
listTime.add(cursor.getString(cursor.getColumnIndex("time")));
cursor.moveToNext();
}
cursor.close();
setDates(listDate);
setScores(listUserProgress);
setTimes(listTime);
}
private void setDates(List<String> dates) {this.dates = dates;}
private void setScores(List<Integer> scores) {this.scores = scores;}
private void setTimes(List<String> times) {this.times = times;}
}
I believe the List<string> variables turn out to be empty.
I wish I could put in more specific code but the google dev console stack trace is unclear.
The error I get is from the google developers console, not android studio, since I cannot replicate the issue on my devices or any emulated devices.
java.lang.RuntimeException:
at android.app.ActivityThread.performResumeActivity (ActivityThread.java:3573)
at android.app.ActivityThread.handleResumeActivity (ActivityThread.java:3613)
at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:2862)
at android.app.ActivityThread.-wrap12 (ActivityThread.java)
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1574)
at android.os.Handler.dispatchMessage (Handler.java:110)
at android.os.Looper.loop (Looper.java:203)
at android.app.ActivityThread.main (ActivityThread.java:6364)
at java.lang.reflect.Method.invoke (Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:1076)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:937)
Caused by: java.lang.IndexOutOfBoundsException:
at java.util.ArrayList.get (ArrayList.java:411)
at org.ieltstutors.academicwordlist.AcademicWordListFragment.showSets12 (AcademicWordListFragment.java)
at org.ieltstutors.academicwordlist.AcademicWordListFragment.access$000 (AcademicWordListFragment.java)
or .access$200 (AcademicWordListFragment.java)
or .drawCircularLockedProgress (AcademicWordListFragment.java)
or .onCreateView (AcademicWordListFragment.java)
or .openCloseSet (AcademicWordListFragment.java)
or .showCircularScores (AcademicWordListFragment.java)
or .showScores (AcademicWordListFragment.java)
or .updateScores (AcademicWordListFragment.java)
at org.ieltstutors.academicwordlist.AcademicWordListFragment.onResume (AcademicWordListFragment.java)
Unfortunately, that is all the information I have on the error.
So the IndexOutOfBoundsException comes from showSets12.
Could the error be from using the wrong context for getInstance? Or maybe from getWriteableDatabase?
Why might the database be accessed correctly some times but not other times? I remember that when the app crashed on my phone when the app started up, I immediately tried to open the app on my friend's phone, but it crashed also, having never crashed before on either. Coincidence?
Any help is greatly appreciated. Thanks!

The fragments onResume() or onPause() will be called only when the Activities onResume() or onPause() is called. They are tightly coupled to the Activity. if you want to do something when your fragment added to activity you can use OnAttach() or other fragment lifecycles
Read this to understand the Fragment Lifecycle

Related

Android Room update query getting stuck on auto generated code

After using Android Room for a few weeks now and getting the hang of basic queries, I've run into an issue with attempting to update a list of custom objects. For some reason when Room tries to create the SQLLite string to insert my new data, it gets stuck with the placeholders:
From the debug window:
Caused by: android.database.sqlite.SQLiteException: near "?": syntax error (code 1): , while compiling: UPDATE player_characters SET ability_scores = ?,?,?,?,?,? WHERE playerCharacterID = ?
#################################################################
Error Code : 1 (SQLITE_ERROR)
Caused By : SQL(query) error or missing database.
(near "?": syntax error (code 1): , while compiling: UPDATE player_characters SET ability_scores = ?,?,?,?,?,? WHERE playerCharacterID = ?)
#################################################################
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:1005)
at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:570)
at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
at android.database.sqlite.SQLiteProgram.(SQLiteProgram.java:59)
at android.database.sqlite.SQLiteStatement.(SQLiteStatement.java:31)
at android.database.sqlite.SQLiteDatabase.compileStatement(SQLiteDatabase.java:1375)
at android.arch.persistence.db.framework.FrameworkSQLiteDatabase.compileStatement(FrameworkSQLiteDatabase.java:62)
at android.arch.persistence.room.RoomDatabase.compileStatement(RoomDatabase.java:204)
at com.pathfinderstattracker.pathfindercharactersheet.database.database_daos.PlayerCharacterDao_Impl.updatePlayerCharacterAbilityScores(PlayerCharacterDao_Impl.java:321)
The DAO that contains the query:
#Dao
#TypeConverters({UUIDConverter.class,
AbilityScoreConcreteConverter.class})
public interface PlayerCharacterDao
{
#Query("UPDATE player_characters "+
"SET ability_scores = :playerCharacterAbilityScores "+
"WHERE playerCharacterID = :characterIDToUpdate")
void updatePlayerCharacterAbilityScores(UUID characterIDToUpdate, List<AbilityScore> playerCharacterAbilityScores);
}
And the repository command that calls it:
private static class updatePlayerCharacterAbilityScoresAsyncTask extends AsyncTask<Object, Void, Void>
{
private PlayerCharacterDao asyncPlayerCharacterDao;
updatePlayerCharacterAbilityScoresAsyncTask(PlayerCharacterDao dao) {asyncPlayerCharacterDao = dao;}
#Override
protected Void doInBackground(final Object... params)
{
UUID playerCharacterID = (UUID)params[0];
List<AbilityScore> updatedAbilityScores = (ArrayList<AbilityScore>)params[1];
asyncPlayerCharacterDao.updatePlayerCharacterAbilityScores(playerCharacterID, updatedAbilityScores);
return null;
}
}
I can confirm that the data is getting to the room query properly, and I've tried passing both concrete and interface objects into the query, as well as had a converter for both individual AbilityScore objects and a list of AbilityScore objects. Any help would be greatly appreciated!
EDIT: A few people have requested the entity that's being updated:
#Entity(tableName = "player_characters")
#TypeConverters({AlignmentEnumConverter.class,
HitPointsConverter.class,
DamageReductionConverter.class,
StringListConverter.class,
UUIDConverter.class,
StringListConverter.class,
AbilityScoreListConverter.class,
CombatManeuverConverter.class})
public class PlayerCharacterEntity
{
#PrimaryKey
#NonNull
private UUID playerCharacterID;
#ColumnInfo(name="character_name")
private String playerCharacterName;
#ColumnInfo(name="character_level")
private int characterLevel;
#ColumnInfo(name="concentration_check")
private int concentrationCheck;
#ColumnInfo(name="character_alignment")
private AlignmentEnum characterAlignment;
#ColumnInfo(name="total_base_attack_bonus")
private int totalBaseAttackBonus;
#ColumnInfo(name="total_hit_points")
private IHitPoints totalHitPoints;
#ColumnInfo(name="total_ac")
private int totalAC;
#ColumnInfo(name="damage_reduction")
private IDamageReduction damageReduction;
#ColumnInfo(name="languages_known")
private List<String> languagesKnown;
#ColumnInfo(name="ability_scores")
private List<IAbilityScore> abilityScores;
#ColumnInfo(name="combat_Maneuver_stats")
private ICombatManeuver combatManeuverStats;
#ColumnInfo(name="spell_resistance")
private int spellResistance;
#ColumnInfo(name="initiative")
private int initiative;
#ColumnInfo(name="fortitude_save")
private int fortitudeSave;
#ColumnInfo(name="reflex_save")
private int reflexSave;
#ColumnInfo(name="will_save")
private int willSave;
~Getters/Setters and Constructors removed for brevity~
}
EDIT: And for good measure I thought I would include the #TypeConverter for AbilityScore (I've reverted this to an earlier form that uses interfaces rather than concrete, since that works elsewhere in the code and difference didn't seem to change anything):
public class AbilityScoreConverter
{
#TypeConverter
public IAbilityScore fromString(String value)
{
IAbilityScore formattedAbilityScore = new AbilityScore();
String[] tokens = value.split(" ");
formattedAbilityScore.setAmount(Integer.parseInt(tokens[0]));
switch(tokens[1])
{
case "STR":
formattedAbilityScore.setStat(AbilityScoreEnum.STR);
case "DEX":
formattedAbilityScore.setStat(AbilityScoreEnum.DEX);
case "CON":
formattedAbilityScore.setStat(AbilityScoreEnum.CON);
case "INT":
formattedAbilityScore.setStat(AbilityScoreEnum.INT);
case "WIS":
formattedAbilityScore.setStat(AbilityScoreEnum.WIS);
case "CHA":
formattedAbilityScore.setStat(AbilityScoreEnum.CHA);
default:
//This may cause issues down the line if a non existent enum gets in the db somehow, but we don't have any error handling yet
//Todo: Add error handling
formattedAbilityScore.setStat(AbilityScoreEnum.STR);
}
return formattedAbilityScore;
}
#TypeConverter
public String toString(IAbilityScore value)
{
return value.toString();
}
}
EDIT: I've cleaned up the logcat text to focus just on the Room/SQLLite issues.
After some searching I was unfortunately forced to give up on updating my db using a #Query command and instead had to fall back on using Rooms default #Update notation. While this does work and properly updates the data in the database, it doesn't allow for me to only update certain fields.
Using ArrayList instead of List in the UPDATE query works for me.
I've followed the solution.
The difference in the Impl build :
MutableList:
#Override
public void test(int tkID, List<Boolean> test) {
StringBuilder _stringBuilder = StringUtil.newStringBuilder();
_stringBuilder.append("UPDATE TasksTable SET test = ");
final int _inputSize = test.size();
StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
_stringBuilder.append(" WHERE taskID = ");
_stringBuilder.append("?");
final String _sql = _stringBuilder.toString();
SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
ArrayList:
#Override
public void test(int tkID, ArrayList<Boolean> test) {
final SupportSQLiteStatement _stmt = __preparedStmtOfTest.acquire();
__db.beginTransaction();
try {
int _argIndex = 1;
final String _tmp;
_tmp = Converters.listBooleanToString(test);
if (_tmp == null) {
_stmt.bindNull(_argIndex);
} else {
_stmt.bindString(_argIndex, _tmp);
}
_argIndex = 2;
_stmt.bindLong(_argIndex, tkID);
_stmt.executeUpdateDelete();
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
__preparedStmtOfTest.release(_stmt);
}
}
As you can see, the ArrayList uses the converter while the MutableList does not.

Should I use an async task on DB Upgrade? - No enclosing instance of type DBAdapter is accessible

I have a database, and I am upgrading the version now. The problem scenario is such that on the DB upgrade I have to read from a file and insert records into a primary database table. Before inserting a record I have to check if this record might already exist and based upon this make a decision of inserting or not inserting. There are tens of thousands of records, which I have to check and insert.
My question here is that would it be right to use a separate thread handler or an Async task on DB upgrade? Or does the system handle this?
Now I have created an AsyncTask
public class AsyncUpgrade extends AsyncTask<Void, Void,String>{
#Override
public void onPreExecute(){
super.onPreExecute();
}
#Override
public void onPostExecute(String result){
//TODO
}
#Override
protected String doInBackground(Void... params) {
System.out.println("UPGRADE 3");
Cursor nameCur = readName();
String name_friend = null;
if(nameCur != null && nameCur.moveToFirst()){
do{
name_friend = nameCur.getString(nameCur.getColumnIndexOrThrow("SelfName"));
}while (nameCur.moveToNext());
}
assets = new AssetsDbHelper(con);
Cursor mCur = assets.getMax_id();
if(mCur != null && mCur.moveToFirst()){
do{
String name = mCur.getString(mCur.getColumnIndexOrThrow("Name"));
String DOB = mCur.getString(mCur.getColumnIndexOrThrow("DOB"));
String imageData = mCur.getString(mCur.getColumnIndexOrThrow("imageUri"));
String type = mCur.getString(mCur.getColumnIndexOrThrow("Type"));
String selection = mCur.getString(mCur.getColumnIndexOrThrow("Radio"));
Cursor checkCur= checkIfRecordExists(name.trim(), DOB.trim());
if(checkCur != null && checkCur.moveToFirst()){
}else{
insertAdhoc(name, DOB,imageData, type, selection, name_friend);
}
}while(mCur.moveToNext());
}
return "Success";
}
}
and I am calling the same from the upgrade method:
if(oldVersion <3){
new AsyncUpgrade().execute();
}
And now to my luck, I have an error popping up:
No enclosing instance of type DBAdapter is accessible. Must qualify the allocation with an enclosing instance of type DBAdapter (e.g. x.new A() where x is an instance of DBAdapter).
Always use an asyctask.Whenever you are doing something which is not related to updating the UI thread.From Android best practices for saving data
Note: Because they can be long-running, be sure that you call getWritableDatabase() or getReadableDatabase() in a background thread, such as with AsyncTask or IntentService.

Randomize different ArrayList to get a value

I searched for related questions but didn´t find a solution (at least i don´t know if i named it correctly)
So, i have two ArrayLists and i would like to randomize all of them to get a value:
public class ListBox {
public static ArrayList listOne(){
ArrayList<Lists> listOne = new ArrayList<>();
listOne.add(new Item("Text One"));
listOne.add(new Item("Text Two"));
return listOne;
}
public static ArrayList listTwo(){
ArrayList<Lists> listTwo = new ArrayList<>();
listTwo.add(new Item("Text Three"));
listTwo.add(new Item("Text Four"));
return listTwo;
}
}
in other activity:
public void buttonClick(View view){
ArrayList<Lists> algumasListas = ListBox.listOne();
...
}
This is where i shuffle it
public class ListMixer extends ListBox{
public ArrayList<Lists> listas = null;
public ListMixer(ArrayList<Lists> listas ) {
this.listas = listas;
}
protected String mixList(){
Double randomNumber = new Double(Math.random() * listas.size());
int randomNum = randomNumber.intValue();
Lista lista= listas.get(randomNum);
String listaString2 = String.valueOf(lista);
String message = ("This is your list: " + listas);
return message;
}
}
my desired output would be one of the four listItems.
Appreciate the help!
Merge arrays into single one of size N.
Choose a random number in range 0..N-1.
Choose an element by index.
The first bug I'm seeing in your code is that listOne() returns object listTwo when called, which doesn't exist. It probably shouldn't even compile, unless something funky is going on with global scope variables.
The following code should do what you want by merging the two lists into one and then returning a random object from them.
public Object randomFromList(List<Object> listOne, List<Object> listTwo){
List<Object> bigList = new ArrayList<Object>(listOne.size() + listTwo.size());
bigList.addAll(listOne);
bigList.addAll(listTwo);
return bigList.get(new Random().nextInt(bigList.size()));
}
For optimization, if you call this a lot, I would save the Random() object outside of the method to avoid instantiating it every time you make the call.

Calling a function in certain intervals

I'm making an android app which displays the list of male and female from database with a user specified age. I almost managed to write the code
the below is my code as far as I made
public class UserList{
private ArrayList<String> maleList;
private ArrayList<String> femaleList;} //getter setter for Arraylists
// method to get data
public UserList getUserLists (Context context, String age){
public UserList userList = new UserList();
public ArrayList<String> maleList = new ArrayList<String>();
public ArrayList<String> femaleList = new ArrayList<String>();
db = dbhelper.getWritableDatabase();
Cursor cursor = db.rawQuery(
"select * from USER_INFO where AGE = ?", new String[]{age});
if(cursor != null)
{
if(cursor.moveToFirst())
{
do{
String user_id = cursor.getString(cursor.getColumnIndex("USER_NAME"));
String gender = cursor.getString(cursor.getColumnIndex("GENDER"));
if(gender.equalsIgnorease("MALE"))
maleList.add(user_id);
else
femaleList.add(user_id);
}while(cursor.moveToNext());
cursor.close();
}
}
userList.setMaleList(maleList);
userList.setFemaleList(femaleList);
return userList;
}
But now I need to call this function for getting data from database to cursor in certain intervals like after every 5 minutes. Please help me in doing that the age variale used is age.
You should implement a broadcast receiver and setup an alarm scheduled at regular interval.
There is a simple tutorial on how to do that here : alarm-manager-example

Filling an object list from MS SQL Server

I've got this user class (this is a reduced version for the example, the real one has more parameters but they are implemented the same way):
public class User {
private int _userID;
private String _fullName;
public User(){
}
public User(int userID, String fullName){
this._userID = userID;
this._fullName = fullName;
}
int getUserID(){
return this._userID;
}
String getFullName(){
return this._fullName;
}
void setUserID(int userID){
this._userID = userID;
}
void setFullName(String fullName){
this._fullName = fullName;
}
}
And I want to retrieve a list from my MS SQL Server of this type of objects in Android, I'm using this method inside the connector helper class (the class in charge to make connection to the server using JDBC):
public List<User> getUsers(int ID){
java.sql.ResultSet result = null;
List<User> users = new ArrayList<User>();
User user = new User();
try {
connection = this.getConnection();
if (connection != null) {
//QUERY
String statement = "SELECT * Users WHERE groupID = "
+ ID;
Statement select = connection.createStatement();
//Calls Query
result = select.executeQuery(statement);
while (result.next()){
user.setUserID(result.getInt("UserID"));
user.setFullName(result.getString("FullName"));
System.out.println(result.getString("FullName"));
//Adds to the list
users.add(user);
}
result.close();
result = null;
closeConnection();
}
else {
System.out.println("Error: No active Connection");
}
} catch (Exception e) {
e.printStackTrace();
}
return users;
}
The data is retrieved well from the server according to the System.out.println I'm using in every iteration of the while, the problem is that the list is always filled with repeated information about the last user I retrieve, to clarify:
If I got users A, B and C, when I read user A, list has this structure:[A], when I read user B, list is:[B,B], when I read user C: [C,C,C], etc. So basically all the objects in the list are being overwritten by the last one read.
I've been struggling with this for hours, hope someone can spot the problem because I can't, thanks in advance for the help.
You instantiate a single User object before your loop, and then modify the same User object at each iteration. So you end up with N times the same User object added to your list. You must recreate a new User at each iteration:
while (result.next()){
User user = new User();
user.setUserID(result.getInt("UserID"));
...

Categories

Resources