I'm new in Libgdx and I'm getting trouble on using a database on my game.
I searched for a tutorial on how to make SQLite work on both Android and Desktop applications using Libgdx but I didn't found a easy one.
The last time I used a database in Android, I created a class that extends from SQLiteOpenHelper.
Is there a simple way to do the same using Libgdx? Or at least, can anyone point me to a step-by-step tutorial or something similar?
EDIT
I forgot to say that I'm looking for something that let me manage versions like SQLiteOpenHelper. In other words, I want to recreate my database in Android on apk installation, when I change the version of my DB on code.
SOLUTION
Following #42n4 answer, I managed how to connect to SQLite Database using SQLiteOpenHelper on Android Application and JDBC on Desktop Application.
First, I created a "common class" for both Desktop and Android Applications:
//General class that needs to be implemented on Android and Desktop Applications
public abstract class DataBase {
protected static String database_name="recycling_separation";
protected static DataBase instance = null;
protected static int version=1;
//Runs a sql query like "create".
public abstract void execute(String sql);
//Identical to execute but returns the number of rows affected (useful for updates)
public abstract int executeUpdate(String sql);
//Runs a query and returns an Object with all the results of the query. [Result Interface is defined below]
public abstract Result query(String sql);
public void onCreate(){
//Example of Highscore table code (You should change this for your own DB code creation)
execute("CREATE TABLE 'highscores' ('_id' INTEGER PRIMARY KEY NOT NULL , 'name' VARCHAR NOT NULL , 'score' INTEGER NOT NULL );");
execute("INSERT INTO 'highscores'(name,score) values ('Cris',1234)");
//Example of query to get DB data of Highscore table
Result q=query("SELECT * FROM 'highscores'");
if (!q.isEmpty()){
q.moveToNext();
System.out.println("Highscore of "+q.getString(q.getColumnIndex("name"))+": "+q.getString(q.getColumnIndex("score")));
}
}
public void onUpgrade(){
//Example code (You should change this for your own DB code)
execute("DROP TABLE IF EXISTS 'highscores';");
onCreate();
System.out.println("DB Upgrade maded because I changed DataBase.version on code");
}
//Interface to be implemented on both Android and Desktop Applications
public interface Result{
public boolean isEmpty();
public boolean moveToNext();
public int getColumnIndex(String name);
public float getFloat(int columnIndex);
[...]
}
}
Then, I created a DatabaseDesktop Class for Desktop Application:
public class DatabaseDesktop extends DataBase{
protected Connection db_connection;
protected Statement stmt;
protected boolean nodatabase=false;
public DatabaseDesktop() {
loadDatabase();
if (isNewDatabase()){
onCreate();
upgradeVersion();
} else if (isVersionDifferent()){
onUpgrade();
upgradeVersion();
}
}
public void execute(String sql){
try {
stmt.execute(sql);
} catch (SQLException e) {
e.printStackTrace();
}
}
public int executeUpdate(String sql){
try {
return stmt.executeUpdate(sql);
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
public Result query(String sql) {
try {
return new ResultDesktop(stmt.executeQuery(sql));
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
private void loadDatabase(){
File file = new File (database_name+".db");
if(!file.exists())
nodatabase=true;
try {
Class.forName("org.sqlite.JDBC");
db_connection = DriverManager.getConnection("jdbc:sqlite:"+database_name+".db");
stmt = db_connection.createStatement();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
private void upgradeVersion() {
execute("PRAGMA user_version="+version);
}
private boolean isNewDatabase() {
return nodatabase;
}
private boolean isVersionDifferent(){
Result q=query("PRAGMA user_version");
if (!q.isEmpty())
return (q.getInt(1)!=version);
else
return true;
}
public class ResultDesktop implements Result{
ResultSet res;
boolean called_is_empty=false;
public ResultDesktop(ResultSet res) {
this.res = res;
}
public boolean isEmpty() {
try {
if (res.getRow()==0){
called_is_empty=true;
return !res.next();
}
return res.getRow()==0;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public boolean moveToNext() {
try {
if (called_is_empty){
called_is_empty=false;
return true;
} else
return res.next();
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public int getColumnIndex(String name) {
try {
return res.findColumn(name);
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
public float getFloat(int columnIndex) {
try {
return res.getFloat(columnIndex);
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
[...]
}
}
And a DatabaseAndroid for Android Application
public class DatabaseAndroid extends DataBase{
protected SQLiteOpenHelper db_connection;
protected SQLiteDatabase stmt;
public DatabaseAndroid(Context context) {
db_connection = new AndroidDB(context, database_name, null, version);
stmt=db_connection.getWritableDatabase();
}
public void execute(String sql){
stmt.execSQL(sql);
}
public int executeUpdate(String sql){
stmt.execSQL(sql);
SQLiteStatement tmp = stmt.compileStatement("SELECT CHANGES()");
return (int) tmp.simpleQueryForLong();
}
public Result query(String sql) {
ResultAndroid result=new ResultAndroid(stmt.rawQuery(sql,null));
return result;
}
class AndroidDB extends SQLiteOpenHelper {
public AndroidDB(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version);
}
public void onCreate(SQLiteDatabase db) {
stmt=db;
DatabaseAndroid.this.onCreate();
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
stmt=db;
DatabaseAndroid.this.onUpgrade();
}
}
public class ResultAndroid implements Result{
Cursor cursor;
public ResultAndroid(Cursor cursor) {
this.cursor=cursor;
}
public boolean isEmpty() {
return cursor.getCount()==0;
}
public int getColumnIndex(String name) {
return cursor.getColumnIndex(name);
}
public String[] getColumnNames() {
return cursor.getColumnNames();
}
public float getFloat(int columnIndex) {
return cursor.getFloat(columnIndex);
}
[...]
}
}
Finally, I changed the Main Classes of both Android and Desktop Applications:
public class Main extends AndroidApplication {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initialize(new MyGame(new DatabaseAndroid(this.getBaseContext())), false);
}
}
public class Main {
public static void main(String[] args) {
new LwjglApplication(new MyGame(new DatabaseDesktop()), "Example", MyGame.SCREEN_WIDTH, MyGame.SCREEN_HEIGHT,false);
}
}
Note that:
I made a version management like the one that happens in SQLiteOpenHelper using the PRAGMA user_version. This way, I just change the version of the DataBase class when I need to upgrade it.
I didn't put all the methods that I made on Result but, I put the ones that I think that are more important.that are more important.
There is an extension (called gdx-sqlite) that I wrote which will do most of the work you require. Latest build of this extension can be downloaded from here. The source code and read me are located at: https://github.com/mrafayaleem/gdx-sqlite
This extension currently supports Android and Desktop platforms. Also, there is no support to open databases located in the assets folder of the Android app. However, this is a pending feature and will be added soon.
Follow the instructions in read me to setup your projects for database handling. Following is an example code:
package com.mrafayaleem.gdxsqlitetest;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.sql.Database;
import com.badlogic.gdx.sql.DatabaseCursor;
import com.badlogic.gdx.sql.DatabaseFactory;
import com.badlogic.gdx.sql.SQLiteGdxException;
public class DatabaseTest {
Database dbHandler;
public static final String TABLE_COMMENTS = "comments";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_COMMENT = "comment";
private static final String DATABASE_NAME = "comments.db";
private static final int DATABASE_VERSION = 1;
// Database creation sql statement
private static final String DATABASE_CREATE = "create table if not exists "
+ TABLE_COMMENTS + "(" + COLUMN_ID
+ " integer primary key autoincrement, " + COLUMN_COMMENT
+ " text not null);";
public DatabaseTest() {
Gdx.app.log("DatabaseTest", "creation started");
dbHandler = DatabaseFactory.getNewDatabase(DATABASE_NAME,
DATABASE_VERSION, DATABASE_CREATE, null);
dbHandler.setupDatabase();
try {
dbHandler.openOrCreateDatabase();
dbHandler.execSQL(DATABASE_CREATE);
} catch (SQLiteGdxException e) {
e.printStackTrace();
}
Gdx.app.log("DatabaseTest", "created successfully");
try {
dbHandler
.execSQL("INSERT INTO comments ('comment') VALUES ('This is a test comment')");
} catch (SQLiteGdxException e) {
e.printStackTrace();
}
DatabaseCursor cursor = null;
try {
cursor = dbHandler.rawQuery("SELECT * FROM comments");
} catch (SQLiteGdxException e) {
e.printStackTrace();
}
while (cursor.next()) {
Gdx.app.log("FromDb", String.valueOf(cursor.getString(1)));
}
try {
dbHandler.closeDatabase();
} catch (SQLiteGdxException e) {
e.printStackTrace();
}
dbHandler = null;
Gdx.app.log("DatabaseTest", "dispose");
}
}
http://marakana.com/techtv/android_bootcamp_screencast_series.html
Class 4, Part 1: Android Bootcamp - statusData, for libgdx: http://code.google.com/p/libgdx-users/wiki/SQLite
EDIT:
I should mention about two new courses about libgdx games at Udacity:
https://github.com/udacity/ud405
https://github.com/udacity/ud406
Related
In the first Jframe I have a JTable filled from database and i need to pass data of the selected jTable to another frame.
So I need to know from another JInternalFrame which row was selected in the First Jframe
public void showTableData() {
try {
Class.forName(driverName);
Connection con = DriverManager.getConnection(url, userName, password);
String sql = "SELECT t.name, t.exam, l.coursename\n"
+ "FROM exam AS t\n"
+ "INNER JOIN Course as l ON (t.LendaID=l.LendaID)";
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
int i = 0;
Jtable1.setModel(DbUtils.resultSetToTableModel(rs));
} catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(null, ex.getMessage(), "Error",
JOptionPane.ERROR_MESSAGE);
}
}
This is the table I've in the first Jframe
Regardless the number of your components, as a simple solution, you can create a CourseEventDispatcher class to be central point to dispatch course events across the application.
public class CourseEventDispatcher {
private List<CourseEventSubscriber> subscribers;
// ...
public void dispatchEvent(CourseEvent event) {
for(CourseEventSubscriber: subscribers) {
if( event.getSource() != subscriber ) {
subscriber.onCourseEvent(event);
}
}
}
}
And for each relevant view, there is a controller which is a CourseEventSubscriber:
public class SomeFrameController implements CourseEventSubscriber {
private CourseEventDispatcher courseEventDispatcher;
public SomeFrameController(CourseEventDispatcher courseEventDispather) {
this.courseEventDispatcher = courseEventDispatcher;
}
public void addSelectionListener() {
// ...
table.getSelectionModel().addListSelectionListener(
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent event) {
doYourOwnStuff();
// then dispatch the event
courseEventDispatcher.dispatch(new CourseEvent(this, event));
}
}
);
}
// from some other view
public void onCourseEvent(CourseEvent event) {
// process the event
// e.g. event.getEvent()
}
}
And CourseEvent is a simple class of
public class CourseEvent {
private CourseEventSubscriber source;
private EventObject event;
public CourseEvent(CourseEventSubscriber source, EventObject event) {
this.source = source;
this.event = event;
}
// getters
}
You can add(register) your controllers after you created a dispatcher.
Hope this gives you another perspective.
I'm using Ormlite, for my android app, to store my data. I want to download a database from a site and after this, the program checks if a database exists. If a database exists, then the new database and the old database should merge.
Here comes my problem. In Ormlite you create a Dao to interact with the tables of a database and I wanted to know, how I can interact with the new database after the download?
For example:
Database A from the app has the items:
Cheese, Eggs, Milk, Chicken
Database B from the download has the items:
Cheese, Eggs, Milk, Flour, Bread
At the end I want:
Cheese, Eggs, Milk, Flour, Bread, Chicken and only one database left.
And to clarify: Yes, it exists questions "How to merge databases".
For example:
Android update - merge database contents?
But that is not my problem. I want to know, how to interact with the new database with Ormlite. Which way exists, that I can get access to the new database?
Edit:
I tried to download the database and guarantee that the version is always higher than the old database version. Therefore, at least that what I thought, it would go in OnUpgrade method and there I could merge the database. But i just replaced the old database.
I also tried to make a second OrmDBHelper with another name, so after the download I could use the methods there. But no errors or anything whatsoever.
Here is the database helper from the existing database and the second one is the same, except the name.
public class OrmDbHelper extends OrmLiteSqliteOpenHelper {
public static final String LOG = OrmDbHelper.class.getName();
public static final String DB_NAME = "bestand.db";
public static final int DB_VERSION = 1;
public OrmDbHelper(Context context) {
super(context, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()
+ File.separator + DB_NAME, null, DB_VERSION);
}
#Override
public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
try {
TableUtils.createTableIfNotExists(connectionSource, ScanItem.class);
TableUtils.createTableIfNotExists(connectionSource, BestandItem.class);
TableUtils.createTableIfNotExists(connectionSource, SettingsItem.class);
} catch (SQLException ex) {
Log.e(LOG, "Error Creating table", ex);
}
}
#Override
public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {
try {
TableUtils.dropTable(connectionSource, ScanItem.class, true);
TableUtils.dropTable(connectionSource, BestandItem.class, true);
TableUtils.dropTable(connectionSource, SettingsItem.class, true);
TableUtils.createTable(connectionSource, ScanItem.class);
TableUtils.createTable(connectionSource, BestandItem.class);
TableUtils.createTable(connectionSource, SettingsItem.class);
} catch (SQLException ex) {
Log.e(LOG, "Error Updating table", ex);
}
}
public Dao<ScanItem, Integer> createScanItemDAO() {
try {
return DaoManager.createDao(connectionSource, ScanItem.class);
} catch (SQLException ex) {
Log.e(LOG, "Error Creating DAO for Todo class", ex);
}
return null;
}
public Dao<BestandItem, Integer> createBestandItemDAO() {
try {
return DaoManager.createDao(connectionSource, BestandItem.class);
} catch (SQLException ex) {
Log.e(LOG, "Error Creating DAO for Todo class", ex);
}
return null;
}
public Dao<SettingsItem, Integer> createSettingItemDAO() {
try {
return DaoManager.createDao(connectionSource, SettingsItem.class);
} catch (SQLException ex) {
Log.e(LOG, "Error Creating DAO for Todo class", ex);
}
return null;
}
}
And here is how I tried to get access to the second database:
The access to the first database works, but to the second one doesnt. I even checked if the download of the second database works, which it does.
public class OrmDataHelper {
private Dao<ScanItem, Integer> scanItemDAO;
private Dao<BestandItem, Integer> bestandItemDAO;
private Dao<SettingsItem, Integer> settingsItemsDAO;
public OrmDataHelper(Context context) {
OrmDbHelper ormDbHelper = new OrmDbHelper(context);
scanItemDAO = ormDbHelper.createScanItemDAO();
bestandItemDAO = ormDbHelper.createBestandItemDAO();
settingsItemsDAO = ormDbHelper.createSettingItemDAO();
}
public ArrayList<ScanItem> getAllScanItem() {
ArrayList<ScanItem> temp = null;
try {
temp = new ArrayList<>(scanItemDAO.queryForAll());
} catch (SQLException ex) {
ex.getMessage();
}
return temp;
}
I have a spring boot application. These are the classes:
RunBatchFile.java
public class RunBatchFile {
private Boolean isSuccessful;
private String content;
public void RunningBatchCommand() {
String filePath = "C:/Users/attsuap1/Desktop/test.bat";
try {
Process p = Runtime.getRuntime().exec(filePath);
int exitVal = p.waitFor();
if (exitVal == 0)
{
isSuccessful = true;
}
else {
isSuccessful = false;
}
System.out.println(isSuccessful);
} catch (Exception e) {
e.printStackTrace();
}
}
public RunBatchFile(Boolean isSuccessful) {
this.isSuccessful = isSuccessful;
this.content = content;
}
public RunBatchFile(String format) {
// TODO Auto-generated constructor stub
}
public Boolean getisSuccessful() {
return isSuccessful;
}
public String getContent() {
return content;
}
}
BatchFileController
#RestController
public class BatchFileController {
private static final String template = "Sum, %s!";
private static boolean isSuccessful;
#RequestMapping("/runbatchfile")
#ResponseBody
public RunBatchFile runbatchFile(#RequestParam(value = "isSuccessful") Boolean isSuccessful) {
return new RunBatchFile(String.format(template, isSuccessful));
}
}
The runBatchFile.java class executes a batch file and will show output as either true or false depending on whether the batch file has executed its commands correctly or not. I want to display that output on a web browser therefore i have created the BatchFileController.java class.
I get the error:
Required Boolean parameter 'isSuccessful' is not present
How do i edit my codes to make this work? Which means, either {true} or {false} is shown on the web browser when i run localhost:8080/runbatchfile?
I'm not quite sure what you are doing. The issue you are having with your controller is that you have defined your method to require a boolean parameter. Given your scenario, that would not make sense, as you would not tell the endpoint the result of the script running; the endpoint tells you that. Your method return type should be boolean instead.
Generally this would be the way to go about this if it is a short running script. I tested with a simple ping command and things worked out. Pointing to an invalid IP failed.
If the script takes a lot of time, you're going to want to go async where you submit a job, and you can check back with a different method to see what the status is.
I would have a class to run your batch file:
public class RunBatchFile {
public boolean runBatch() {
String filePath = "C:/Users/attsuap1/Desktop/test.bat";
try {
Process p = Runtime.getRuntime().exec(filePath);
int exitVal = p.waitFor();
return exitVal == 0;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
And then in your controller:
#RequestMapping("/runbatchfile")
public boolean runbatchFile() {
RunBatchFile rbf = new RunBatchFile();
return rbf.runBatch();
}
If you want to wrap your results so your response isn't just a true/false string. Note that the return type of the method has changed to a simple POJO:
Class
public class RunBatchFile {
public ResultWrapper runBatch() {
String filePath = "C:/Users/attsuap1/Desktop/test.bat";
try {
Process p = Runtime.getRuntime().exec(filePath);
int exitVal = p.waitFor();
return new ResultWrapper(exitVal == 0);
} catch (Exception e) {
e.printStackTrace();
return new ResultWrapper(false);
}
}
}
Wrapper Class
public class ResultWrapper {
private boolean result;
public ResultWrapper(boolean result) {
this.result = result;
}
public boolean getResult() {
return result;
}
}
Controller Method
#RequestMapping("/runbatchfile")
public ResultWrapper runbatchFile() {
RunBatchFile rbf = new RunBatchFile();
return rbf.runBatch();
}
So i want to implement a methode which allow me to prepare a test automatically
a test is an set of question and the test must be preparated automatically
So the questions will be displayed in random manner from the database.
I know that the sql query is like this :
SELECT q FROM Question ORDER BY RAND() LIMIT ...;
this the package who concern all services offered by Class Test
package tn.esen.services.gestion.test;
#Stateless
public class GestionTest implements GestionTestRemote, GestionTestLocal {
#PersistenceContext
private EntityManager entityManager;
/**
* Default constructor.
*/
public GestionTest() {
// TODO Auto-generated constructor stub
}
#Override
public boolean addTest(Test test) {
try {
entityManager.persist(test);
return true;
} catch (Exception e) {
System.err.println("Impossible de créer un Test");
} return false;
}
#Override
public boolean updateTest(Test test) {
try {
entityManager.merge(test);
return true;
} catch (Exception e) {
System.err.println("Impossible de mettre à jour un test");
} return false;
}
#Override
public boolean deleteTest(Test test) {
try {
entityManager.remove(entityManager.merge(test));
return true;
} catch (Exception e) {
System.err.println("Impossible de supprimer un test");
} return false;
}
#Override
public List<Test> readAllTest() {
String jpql = "select t from Test t ";
Query query = entityManager.createQuery(jpql);
return query.getResultList();
}
#Override
public Test findTestById(Integer id) {
return (Test) entityManager.find(Test.class, id);
}
#Override
public List<Test> findAllTestByCandidat(Candidat candidat) {
Query query=entityManager.createQuery("select distinct t from Test t join t.resultats r where r.candidat=:cand");
query.setParameter("cand", candidat);
return query.getResultList();
Solution
Assume you have a Question Entity for your Question Table, the fetch is very easy, just fetch any (number_of_questions) from a list of questions in the question table at RANDOM.
public List<Question> prepareRandomTest(int number_of_questions){
String sql = "SELECT * FROM question ORDER BY RANDOM() LIMIT "+number_of_questions;
SQLQuery query = session.createSQLQuery(sql);
query.addEntity(Question.class);
List<Question> results = (List<Question>)query.list();
return result;
}
I have these 4 methods which each close a different connection type, meaning each of those has a different input object. Each method calls the close() method on the provided input object. Is there a way to combine those methods into one, taking in a generic object? I have no way to implement an interface on the provided objects or extend them.
import java.sql.Statement;
import javax.jms.Connection;
import javax.mail.Transport;
private void close(Statement stm) {
if(stm == null) {
return;
}
try {
stm.close();
} catch (SQLException ex) {
logger.error("Error while closing statement", ex);
}
}
private void close(java.sql.Connection con) {
if(con == null) {
return;
}
try {
con.close();
} catch (SQLException ex) {
logger.error("Error while closing connection", ex);
}
}
private void close(javax.jms.Connection con) {
if(con == null) {
return;
}
try {
con.close();
} catch(JMSException ex) {
logger.error("Error while closing JMS connection", ex);
}
}
private void close(Transport transport) {
if(transport == null) {
return;
}
try {
transport.close();
} catch (MessagingException ex) {
logger.error("Error while closing mail transport", ex);
}
}
EDIT:
Thank you for your answers regarding Java 1.7. Unfortunately our servers are running Java 1.6, so is there any solution for that?
You can use reflection.
First sample give you support for try() with resources:
#Test
public void testRes() throws Exception {
try(ClosableWrapper<StringWriter> rs = new ClosableWrapper<>(new StringWriter())){
Writer wr = rs.getResource();
}
}
static class ClosableWrapper<T> implements AutoCloseable{
private T resource;
private String closeMethod = "close";
public ClosableWrapper(T resource) {
this.resource = resource;
}
public ClosableWrapper(T resource, String closeMethod) {
this.resource = resource;
this.closeMethod = closeMethod;
}
public T getResource() {
return resource;
}
#Override
public void close() throws Exception {
if(resource!=null){
Method m = resource.getClass().getMethod(closeMethod);
m.invoke(resource);
}
}
}
or just one method:
public void close(Object resource) throws Exception {
if(resource!=null){
Method m = resource.getClass().getMethod("close");
m.invoke(resource);
}
}
Assuming these are your classes, use the AutoCloseable interface and put them in a try-with-resource.