Java Question B4A IDE + Android Studio = Synergistic Ecosystem

cimperia

Active Member
Licensed User
Longtime User
I’m afraid this post will only be of interest to developers conversant with or interested in Android Studio. It tries to show how to harness some AS features to create a synergistic ecosystem with B4A IDE.

While B4A source code was proprietary, when I wanted to add functionality to a specific B4A internal library, I had to use reflection, write inline Java or write a new Java class and wrapper to extend the B4A class.

When using the latter, I had to refactor my code so that it used the new class. With B4A being now open-source, you can very easily add extra functionality directly to the internal B4A class and keeps everything tidy.

To Android Studio (AS) users, I want to share with you a couple of techniques that I use when I want to write Java libraries and run and test the B4A application within AS itself.

It might not be widely known, but you can actually develop pure Java code (jar) with AS with no Android app/code whatsoever - AS is a cut-down version of JetBrain’s IntelliJ IDEA, after all. But what’s more interesting is to use Gradle to automate the generation of the xml for the wrapping jar to be consumed by the B4A IDE and the copying of both jar and xml files to whatever folder you want.

Nevertheless, this project will have an Android app dependent on a Java library to test the B4A code directly within AS. I’ll outline the steps to “import” a B4A project into AS.

This set-up (AS + Gradle) is a flexible alternative to the combination of Eclipse + B4A Simple Library Compiler to compile, create and copy the xml and jar files: Simple Library Compiler.

To illustrate the techniques, I’ve used Klaus excellent SQLiteLight2 demo B4A project that you can download from here: SQLiteLight Projects

Here’s the direct link: SQLiteLight2.zip

Rather than describing the steps in detail, I’ll outline them and post, at the bottom of this tutorial, the AS project which relies on the latest version of AS 3.61. Download the project, drop it in your AS workspace and the app should compile and run out of the box. The Gradle code will needs editing with your B4A libraries path.

So, what have I set out to do in this project?

1. Add functionality to the B4A internal SQL library which is a light wrapper for the Android SQLite library.
2. Use AS to edit and automate the compilation and generation of the jar and xml files and their copying to a specified folder.
3. Import B4A SQLigeLight2 project into AS and test the new functionality from AS.

Step 3. is of course not necessary as you could test directly from B4A, but I wanted to quickly show how it can be done as there’ve been cases when it has been necessary for me to do so.

So, let’s start:

1. Add functionality to B4A SQL internal library.

Download the SQL folder from Github . The simplest way is actually to download the whole B4A project B4A in zip format and extract the required folder, i.e. Libs_SQL.

1. Make sure that you’re using the latest version of AS.

2. Start up AS and create a new Android project called SQLiteLight2 choosing Basic Activity template.

3. Create a 2nd module as a Java/Kotlin library, name it SQL and copy SQL.java into it.

4. Create a dependency between app (the default AS application) and the SQL module.

5. Add the required jar dependencies (android.jar, Core.jar, BA4Shared.jar and for this particular project Reflection.jar, JavaObjects.jar, XUI.jar and StringUtils.jar)

6. Edit SQL.java.

In the Edit.bas file of Klaus’s SQLiteLight2 project, there’s the following bit of code to add a new person row. The code first inserts a new row then executes a query to retrieve its ROWID.
B4X:
'if not, add the entry
'we use ExecNonQuery2 because it's easier, we don't need to take care of the data types
Query = "INSERT INTO persons VALUES (?, ?, ?)"

Main.SQL1.ExecNonQuery2(Query, Array As String(edtFirstName.Text, edtLastName.Text, edtCity.Text))                  

'to display the rowid of the last entry we read the max value of the rowid column
RowID = Main.SQL1.ExecQuerySingleResult("SELECT max(rowid) FROM persons")

BTW, a quick remark here. I suggest that
B4X:
SELECT last_insert_rowid()  FROM persons”
Is more idiomatic to retrieve the row id of the last inserted row. Results are identical but there are a couple of subtleties to be aware of.

But it so happens that the Android library has a method that inserts a single row and returns the ROWID, which is faster, safer and requires less code.

Another need that I often have in my code is to find out how many rows were affected by an Insert, Update or Delete statement. Such a method exists natively so it’s easy to use.

Here’s the java code added to the B4A file SQL.java. It’s a minor editing of two copied/pasted methods of the original SQL.java and making a call to the relevant Android method. Nothing earth shattering but it makes your BA4 code that simpler.

B4X:
//---------------------
// User defined methods
//---------------------
/**
* Execute an Insert SQL statement and return the ID of the row inserted due to this call.
*
*The statement can include question marks which will be replaced by the items in the given list.
*Note that Basic4android converts arrays to lists implicitly.
*The values in the list should be strings, numbers or bytes arrays.
*Example:<code>
*SQL1.ExecInsert("INSERT INTO table1 VALUES (?, ?, 0)", Array As Object("some text", 2))</code>
*/
public long ExecInsert(String Statement, List Args) {
   SQLiteStatement s = db.compileStatement(Statement);
   try {
      int numArgs = Args.IsInitialized() == false ? 0 : Args.getSize();
      for (int i = 0; i < numArgs; i++) {
         DatabaseUtils.bindObjectToProgram(s, i + 1, Args.Get(i));
      }
      return s.executeInsert();
   } finally {
      s.close();
   }
}
//
/**
* Execute an Insert, Update Or Delete SQL statement and return the number of rows affected.
*
*The statement can include question marks which will be replaced by the items in the given list.
*Note that Basic4android converts arrays to lists implicitly.
*The values in the list should be strings, numbers or bytes arrays.
*Example:<code>
*SQL1.ExecInsertUpdateDelete("INSERT INTO table1 VALUES (?, ?, 0)", Array As Object("some text", 2))</code>
*/
public int ExecInsertUpdateDelete(String Statement, List Args) {
   SQLiteStatement s = db.compileStatement(Statement);
   try {
      int numArgs = Args.IsInitialized() == false ? 0 : Args.getSize();
      for (int i = 0; i < numArgs; i++) {
         DatabaseUtils.bindObjectToProgram(s, i + 1, Args.Get(i));
      }
      return s.executeUpdateDelete();
   } finally {
      s.close();
   }
}

In B4A, you’d update the code to this:
B4X:
'if not, add the entry
'we use ExecInsert because it's easier, we don't need to take care of the data types
Query = "INSERT INTO persons VALUES (?, ?, ?)"

RowID = Main.SQL1. ExecInsert (Query, Array As String(edtFirstName.Text, edtLastName.Text, edtCity.Text))

At this stage, it would be possible to compile the library by simply building the project, but you’d have to do a lot of work manually to generate the XML file. So, let’s call on Gradle to automat this for us.

2. Use AS & Gradle to automate the compilation and generation of the jar and xml files.

To the Gradle build file add this bit of Groovy code (or you could use Kotlin instead) which generates the xml and copies it along with the jar to the specified folder. All the necessary variables are defined in the ext enclosure. Modify them to suit your environment. The main ones you really need to concern yourself with are the ones pertaining to B4A internal and shared library library paths. You can reuse this piece of code in other projects with little change.
B4X:
//------------------------ JAVADOC Processing ------------------------------------------------------
// Define and initialize all variables here
ext {
    AppRootDirLib = project.rootDir.absolutePath + '/SQL/build/libs/'
    AppRootDirSrc = project.rootDir.absolutePath + "/SQL/src/main/java/anywheresoftware/B4A/sql"
    inJar = 'SQL.jar'
    inJarFullPath = AppRootDirLib + inJar
    outJar = 'SQL.jar'
    outJarFullPath = AppRootDirLib + outJar

    //B4A library and project shared library paths
    BASharedLibrary = "C:/Projects/B4A/SharedLibraries/"
    BALibs = "C:/Program Files (x86)/Anywhere Software/Basic4android/Libraries/"
    //BA4 doclet path
    BADocletPath = files("C:/Projects/SimpleLibraryCompiler/BADoclet").asList()

    //XML file outpout dir
    B4AXMLDir = file("../B4AXML/")
    //XML file name
    docXML = file("$B4AXMLDir/SQL.xml")
}

configurations {
    BADoclet
}

dependencies {
    BADoclet (fileTree(dir: "$BALibs",includes: ["**/Core.jar", "**/B4AShared.jar"]))
    BADoclet (fileTree(dir: "build/libs/SQL.jar"))
}

task makeAndCopyB4AXML(type: Javadoc ) {
    verbose = false

    if (!file(inJarFullPath).exists()){
        println ("---------------------------------------------------------")
        println ('********** SQL.jar is missing. Rebuild project **********')
        println ("---------------------------------------------------------")
        return false
    }

    if (file(docXML).exists()) {
        docXML.delete()
        println("Deleted doc file: " + docXML.name)
    }

    source = fileTree(dir: "$AppRootDirSrc", includes: ["**/*.java"])
    classpath = project.files(android.getBootClasspath().join(File.pathSeparator))
    classpath += configurations.BADoclet

    destinationDir = B4AXMLDir
    println("B4AXML Dir:" + destinationDir)

    options.doclet("BADoclet")
    options.doclet("BADoclet")
    options.docletpath = BADocletPath
    options.addStringOption("B4Atarget", "$docXML")
    options.addStringOption("B4Adebug")
    options.setMemberLevel(JavadocMemberLevel.PUBLIC)

    doLast {
        if (!file(docXML).exists()) {
            println ("--------------------------------------------------")
            println("Error: " + docXML.name + " failed to get generated.")
            println ("---------------------------------------------------")
            return false
        }
        copy {
            from docXML
            into BASharedLibrary
        }

        copy {
            from(AppRootDirLib)
            into(BASharedLibrary)
            include(inJar)
            rename(inJar, outJar)
          }
          println ("----------------------------------------------------")
    println ("******** Remember to refresh your libraries ********")
    println ("----------------------------------------------------")
    }
    failOnError true
}


3. Import SQLigeLight2 B4A project into AS, test the new functionality in AS.

This step is optional and you may wonder in what circumstances you’d need to test your code from AS. Most of the time you don’t, though it has helped me in some situations where I had intractable bugs that were coming from Java libraries. Android Studio will decompile the Java class if no source is available and this is very useful in hard to crack bugs.

But also, when the code is simple, I prefer to amend the code directly in AS so that I can test the java libraries without the back and forth between B4A & AS and recompilation/run cycles.

In the project I’ve posted below, as mentioned, I’ve “imported” B4A SQLiteLight2. Here are the steps, in a nutshell:

1. Within AS, select Android from project dropdown and delete the source files generated by AS.

2. Under app/java creates a new package path called b4a.sqlitelight2

3. Generate your project with B4A and under the Objects/src/b4a/sqlitelight2 folder copy the 3 java files and past them within AS under b4a.sqlitelight2.

4. Replace the content of the AS AndroidManifest.xml file with B4A AndroidManifest.xml

5. Within AS, select Project from project dropdown.

6. Open the apk file produced by B4A and extract the assets folder that you copy and paste to AS directly under app/src/main/

7. Extract the res folder and copy its subfolders in AS

8. Add the necessary external jar dependencies, ie here Reflection.jar, JavaObjects.jar, XUI.jar and StringUtils.jar

9. Set the build variant to release and sign the apk file.

10. Build and run the app.

1584956909722.png


Rather than to go into minutia and make a long tutorial very long, I’ve decided to outline the steps and post the AS project that should run as is from your AS workspace. Make sure to choose the release variant, if not already set, before building and running, as I have not tested the ‘debug’ variant.

If you study the AS project closely, you’ll have all the information necessary to create your own projects.

Also, for any new project, rather than starting from scratch, you’ll only need to copy the relevant files (java, assets etc) into a copy of the AS project and use AS refactoring to rename packages, folders, modules and project names. It’s the quickest way to do it.

One drawback with this method of directly modifying B4A internal java libraries is that you’ll need to use software such as Winmerge to (painlessly) manage changes with new B4A releases.

You can download SQLiteLight2.zip for the Android Studio project from HERE.

PS
Android Studio is “heavy” and requires a fast machine with fast disk access and lots of RAM to work properly. On the other hand, Visual Studio Code (VSC) (not to be confused with Visual Studio IDE) is a light and very fast code editor which is a joy to work with. You can configure it to use Java and Gradle. You can “point” VSC to the Android folder or make a copy and VSC will generate the xml and jar. It’s an alternative for developers who can’t or won’t install AS on their dev. machine. I often use it when I only need to do a quick fix to a Java file and recompile the project.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
I misread your post. I thought that you were talking about extending the generated source code which is a bad idea.
Many developers use AS to build libraries. You can use any editor you like. You can also use SLC to compile and build the XML. It is very simple.
I recommend changing the name of internal libraries if you modify them.
 
Top