Android Tutorial [java] Creating libraries for B4A

Erel

B4X founder
Staff member
Licensed User
Longtime User
New video tutorial is available here: http://www.b4x.com/forum/libraries-...1-video-tutorial-creating-simple-library.html

Basic4android has a very good support for external libraries. In fact almost all of the internal keywords are loaded from an external library (Core library).

In order to develop a library you need to have Eclipse installed.
The Android plug-in is not required as the libraries are regular Java libraries.

Knowledge required:
- Java
- Android main concepts: http://www.b4x.com/forum/basic4andr...87-android-process-activities-life-cycle.html

The IDE is a .Net process. It cannot load or inspect jar files. Therefore together with each jar file there is an XML file that describes the file to the IDE. This file also holds the documentation for the online help.
The XML file is created by using a custom Javadoc tool.

Creating a new project:
1. Create a new Java project (not Android project).
2. Add a reference to the following jars: android.jar, B4AShared.jar and optionally to Core.jar.
This is done by right clicking on the project name - Build Path - Add External Jars.

android.jar is part of the Android SDK.
By default you should choose the android.jar that is located under <android-folder>\platforms\android-4.
That way your library will work with Android v1.6 and above (which is the same as the other libraries). If you don't see android-4 then you need to download this SDK with the SDK manager.
B4AShared.jar and Core.jar are located in Basic4android libraries folder.

An updated B4AShared.jar is included in the attached file. This is a minor update which is only relevant if you are using Basic4android v1.00.

Writing code:
All public classes and members are exposed to B4A.
Members can be either static or non-static. In both ways the user accesses the members with the 'Dimmed' object.
Only the no arguments constructor will be called.
The constructor is called when the object is 'Dimmed'.
Consider the following code:
B4X:
Dim b As Button
b = SomeList.Get(5)
'now user uses b to access the button.
In this code, b is declared to allow the user to access a different button.
So it would have been a waste if the Button constructor already called the android button constructor (the B4A button is wrapper above the standard button).
This is why many types have an Initialize method. In that cases the Initialize actually calls the standard button constructor.
Actually there is a special class which you can inherit from, for simple wrappers. I will later cover it.

Properties - Methods that start with 'get' and don't have any parameters will be converted to a property. The same is true for methods that start with 'set' and accept one parameter. The parameter types should match if both get and set exist.
B4X:
public int getWidth() {
 
}
public void setWidth(int value) {
}
=> will appear as Width property which is readable and writable.

BA - BA is a special object that you can use to raise events and to get access to the user activity, application context and other resources.
The compiler is responsible for passing this object. The IDE doesn't expose it.
For example, this is GPS.Start signature:
B4X:
public void Start(BA ba, long MinimumTime, float MinimumDistance) {
Here BA is used to raise the GPS events.

And now for something a bit more complicated. There are actually two BA objects for each activity. One is the "process BA" and the other is the "activity BA".
The process BA doesn't hold an activity context (or an activity object). The activity BA does hold a reference to the activity.

If your object is an "Activity object" then you will get the activity BA. Otherwise you will get the process BA.
Activity objects cannot be declared in Sub Process_Globals (as this would cause a memory leak).

You should prefer to create process objects. Only create Activity objects if it is really needed (mainly for UI elements). Note that the ApplicationContext is accessible from the process BA.

Annotations
So assuming that you are still following, the question that should be asked is how do you mark your object as an Activity object? This is done with annotations.
B4AShared.BA has several important annotations.
@ActivityObject - Marking your class with this annotation will make it an activity object.
Additional important annotations:
@ShortName - In order for a class to be accessible it must has a short name annotation. This annotation which receives one string parameter defines the name that the user will see. The user is not familiar with the package or the actual class name. This means that you should not use common names as the short names, otherwise there will be conflicts in the future. You can use a prefix like: eu_FTP.
Here is the declaration of the GPS class:
B4X:
/**
* The main object that raises the GPS events.
*/
@ShortName("GPS")
@Permissions(values={"android.permission.ACCESS_FINE_LOCATION"})
@Events(values={"LocationChanged (Location1 As Location)",
        "UserEnabled (Enabled As Boolean)",
        "GpsStatus (Satellites As List)"})
@Version(1f)
public class GPS {
Again the name of this class could have been different.
@Permissions - An array of strings with the permissions that will be added to the manifest file when the user declares an object of this type.
@Events - An array of strings with the events that are raised by this type.
@Version - Sets the library version. Should only be declared once per library (a library can have many classes).
@Hide - Hides public classes or members.
@author - Receives one string parameter with the author name. This will affect the online documentation (this is not available in B4AShared that comes with v1.00, it is available in the attached file).
NEW - @DependsOn - An array of strings with additional jar files that will be referenced when this library is referenced. Removes the need to add "dummy" xml files.

Documentation

All the documentation is done inside the java classes.
You should use regular javadoc style comments to document methods, fields and classes. Do not use any special tags like @param or @return.
You can use code tags:
B4X:
/**
* Reads the file and returns its content as a string.
*Example:<code>
*Dim text As String
*text = File.ReadString(File.DirRootExternal, "text files/1.txt")</code>
*/
The code will be syntax highlighted automatically.
You can also use link tags:
B4X:
/**
     * Creates a new file and writes the given map. Each key value pair is written as a single line.
     *All values are converted to strings.
     *See this link for more information about the actual format: <link>Properties format|http://en.wikipedia.org/wiki/.properties</link>.
     *You can use File.ReadMap to read this file.
     */
    public static void WriteMap(String Dir, String FileName, Map Map) throws IOException {
The format is: Link title|URL.

If you have several objects and want to create a library scope documentation you should add a method with the following signature:
B4X:
/**
     * The HTTP library allows you to communicate with web services and to download resources from the web.
     *As network communication can be slow and fragile this library handles the requests and responses in the background and raises events when a task is ready.
     *There are two HTTP examples which demonstrates this library: <link>Currency Converter|http://www.b4x.com/forum/basic4android-getting-started-tutorials/6506-currency-converter-http-web-services-more.html</link> and a more complex example: <link>Flickr Viewer|http://www.b4x.com/forum/basic4android-getting-started-tutorials/6646-flickr-viewer.html</link>.
     */
    public static void LIBRARY_DOC() {
       
    }
Raising events


There are three methods available for raising events:
- raiseEventFromUI: this is the default way to raise an event from a UI object. It sends a message to an internal queue with the event information.
There is no return value as the event is not raised immediately. The reason behind this method is to avoid conflicts with the way Android handles UI events. See this link for more information.
- raiseEvent: directly calls the given sub.
- raiseEventFromDifferentThread - this method should be used when the event is raised from a different thread. The event message will be delegated to the main thread message queue. You can pass null and 0 in the container and TaskId parameters. They are not needed.

Common parameters:

sender - Is the object that the user will get with the Sender keyword.
event - This is the event sub name. This string must be lower cased and it should contain the prefix name and the event.
params - list of objects to pass as the event parameters.
You should make sure that the string is lower cased or it will not find the sub.

For example, this code checks if the user has a sub with the correct signature and then adds the listener:
B4X:
if (ba.subExists(eventName + "_click")) {
            getObject().setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View v) {
                    ba.raiseEvent(getObject(), eventName + "_click");
                }

            });
        }

New tool to simplify the building process: http://www.b4x.com/forum/showthread.php?p=173756

Generating the XML file
This requires some configuration on the first time.
We are using a custom "doclet" to generate the XML file.
Choose Project - Generate JavaDoc.

libs_2.png


Choose custom doclet and set the name to BADoclet and path to the doclet folder in the downloaded file.
Make sure that the correct project is marked.
Press Next.

libs_3.png


Add a parameter named -b4atarget and write the path of the target file.
Press Finish and the XML should be generated.

You should create the XML in your 'additional libraries' folder.
The jar file should also be copied to this location.
There are several ways to build the jar file.
Personally I'm using a batch file that creates the jar file and copies it to the libraries folder. I'm using a nice utility named AutoHotKey to assign a hot key for this batch file. So I first run the Javadoc tool from eclipse, then press on the hotkey which creates the jar file and copies it to the libraries folder and then right click on the libraries tab in the IDE - Refresh to reload the libraries. From time to time you will get strange MethodNotFoundErrors when you run your program. This happens when you forget to recreate the jar files or you forget to refresh the libraries (the IDE uses a modified Dexer that caches the libraries in order make compilation quicker).

AbsObjectWrapper

If your class is a simple wrapper above another android class then you can inherit from AbsObjectWrapper. The compiler treats such classes differently and automatically converts between the wrapper and the underlying object as required.
It is better not to use AbsObjectWrapper than to use it wrong.
For example, this is the RectWrapper class:
B4X:
@ShortName("Rect")
    public static class RectWrapper extends AbsObjectWrapper<Rect> {
        public void Initialize(int Left, int Top, int Right, int Bottom) {
            Rect r = new Rect(Left, Top, Right, Bottom);
            setObject(r);
        }
        public int getLeft() {return getObject().left;} public void setLeft(int Left) {getObject().left = Left;}
        public int getTop() {return getObject().top;} public void setTop(int Top) {getObject().top = Top;}
        public int getRight() {return getObject().right;} public void setRight(int Right) {getObject().right = Right;}
        public int getBottom() {return getObject().bottom;} public void setBottom(int Bottom) {getObject().bottom = Bottom;}
        /**
         * Returns the horizontal center.
         */
        public int getCenterX() {return getObject().centerX();};
        /**
         * Returns the vertical center.
         */
        public int getCenterY() {return getObject().centerY();};

    }
The user can pass objects from this class whenever a standard Rect is expected. There are additional advantages for AbsObjectWrapper.

Basic4android has internal support for running tasks in the background. This will be covered in the future.

Some examples:
PhoneAccelerometer code:
B4X:
/**
     * This object gives access to the internal accelerometers sensors.
     *See the <link>Orientation and accelerometers example|http://www.b4x.com/forum/basic4android-getting-started-tutorials/6647-orientation-accelerometer.html</link>.
     *This object should be declared as a process global object.
     */
    @ShortName("PhoneAccelerometer")
    @Events(values={"AccelerometerChanged (X As Float, Y As Float, Z As Float)"})
    public static class PhoneAccelerometer {
        private SensorEventListener listener;
        /**
         * Starts listening for AccelerometerChanged events.
         */
        public void StartListening(final BA ba, String EventName) {
            SensorManager sm = (SensorManager) ba.context.getSystemService(Context.SENSOR_SERVICE);
            final String s = EventName.toLowerCase(BA.cul) + "_accelerometerchanged";
            listener = new SensorEventListener() {

                @Override
                public void onAccuracyChanged(Sensor sensor, int accuracy) {
                   
                }

                @Override
                public void onSensorChanged(SensorEvent event) {
                    ba.raiseEvent(this, s, event.values[0], event.values[1], event.values[2]);
                }
               
            };
            sm.registerListener(listener, sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
        }
        /**
         * Stops listening for events.
         */
        public void StopListening(BA ba) {
            if (listener != null) {
                SensorManager sm = (SensorManager) ba.context.getSystemService(Context.SENSOR_SERVICE);
                sm.unregisterListener(listener);
            }
        }
    }
PhoneSMS:
B4X:
@ShortName("PhoneSms")
    @Permissions(values={"android.permission.SEND_SMS"})
    public static class PhoneSms {
        /**
         * Sends an Sms message. Note that this method actually sends the message (unlike most other methods that
         *create an intent object).
         */
        public static void Send(String PhoneNumber, String Text) {
            SmsManager sm = SmsManager.getDefault();
            sm.sendTextMessage(PhoneNumber, null, Text, null, null);
        }
    }
Email:
B4X:
    /**
     * Using an Email object you can create an intent that holds a complete email message.
     *You can then launch the email application by calling StartActivity. Note that the email will not be sent automatically. The user will need to press on the send button.
     *Example:<code>
     *Dim Message As Email
     *Message.To.Add("SomeEmail@example.com")
     *Message.Attachments.Add(File.Combine(File.DirRootExternal, "SomeFile.txt"))
     *StartActivity(Message.GetIntent)</code>
     */
    @ShortName("Email")
    public static class Email {
        public String Subject = "";
        public String Body = "";
        public anywheresoftware.b4a.objects.collections.List To = new anywheresoftware.b4a.objects.collections.List();
        public anywheresoftware.b4a.objects.collections.List CC = new anywheresoftware.b4a.objects.collections.List();
        public anywheresoftware.b4a.objects.collections.List BCC = new anywheresoftware.b4a.objects.collections.List();
        public anywheresoftware.b4a.objects.collections.List Attachments = new anywheresoftware.b4a.objects.collections.List();
        public Email() {
            To.Initialize();
            CC.Initialize();
            BCC.Initialize();
            Attachments.Initialize();
        }
        /**
         * Returns the Intent that should be sent with StartActivity.
         */
        public Intent GetIntent() {
            Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE);
            emailIntent.setType("plain/text");
            emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
                    To.getObject().toArray(new String[0]));
            emailIntent.putExtra(android.content.Intent.EXTRA_CC,
                    CC.getObject().toArray(new String[0]));
            emailIntent.putExtra(android.content.Intent.EXTRA_BCC,
                    BCC.getObject().toArray(new String[0]));
            emailIntent.putExtra(Intent.EXTRA_SUBJECT, Subject);
            emailIntent.putExtra(Intent.EXTRA_TEXT, Body);
            ArrayList<Uri> uris = new ArrayList<Uri>();
            for (Object file : Attachments.getObject())
            {
                File fileIn = new File((String)file);
                Uri u = Uri.fromFile(fileIn);
                uris.add(u);
            }
            emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
            return emailIntent;
        }
    }
The Email example is interesting as we are using the regular B4A lists to hold the recipients. The user will write code similar to:
B4X:
Email1.To.Add(...)
This is useful as we can reuse the functionality of other objects this way and don't need to duplicate existing functionality.

Questions related to libraries development should be asked here: https://www.b4x.com/android/forum/threads/java-creating-libraries-for-basic4android.6810/

See this thread for the default license: http://www.b4x.com/forum/libraries-...important-notice-about-libraries-license.html
 

Attachments

  • BADoclet.zip
    8.5 KB · Views: 5,710
Last edited:

agraham

Expert
Licensed User
Longtime User
From time to time you will get strange MethodNotFoundErrors when you run your program. This happens when you forget to recreate the jar files or you forget to refresh the libraries.
And sometimes if you have changed just the logic of a method and not refreshed the libraries you will get no error and will puzzle for ages why it is not doing what you expected until you realise why. Damn, it's using the old cached jar not the new one! :(
 

susu

Well-Known Member
Licensed User
Longtime User
Uh oh... To me it's too complicated :(
Just wish someone can explain how to create B4A library by using this free library (AndroidLibs - ContactsLib)
 

alwaysbusy

Expert
Licensed User
Longtime User
Hi,

I'm trying to make a library and everything looks ok, except the program crashes when it runs on the emulator. it crashes on 'Dim MyFTP As ABFTPClient' (ABFTPClient is checked in the referenced libraries).

b4a code:

B4X:
'Activity module
Sub Process_Globals
   'These global variables will be declared once when the application starts.
   'These variables can be accessed from all modules.
   
End Sub

Sub Globals
   'These global variables will be redeclared each time the activity is created.
   'These variables can only be accessed from this module.

   Dim Button1 As Button
   Dim MyFTP As ABFTPClient
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.LoadLayout("FTPTest")
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub Button1_Click
   
End Sub

ABFTPClient.java code in eclipse:

B4X:
package com.AB.FTPClient;

import anywheresoftware.b4a.BA.ShortName;
import anywheresoftware.b4a.BA.Version;
import org.apache.commons.net.ftp.*;

@ShortName("ABFTPClient")
@Version(value = 1)
public class ABFTPClient {
   public boolean isConnected=false;
   FTPClient con = new FTPClient();
   public void ABFTPConnect(String host, String login, String pwd){
      try
      {
          con.connect(host);
          if (con.login(login, pwd))
          {
              con.enterLocalPassiveMode(); // important!
              isConnected=true;
          }
      }
      catch (Exception e)
      {
          e.printStackTrace();
      }
   }
   public void ABFTPUpload() {
      
   }
   public void ABFTPDownload() {
      
   }
   public void ABFTPDisconnect() {
      if (isConnected==true){
         try
         {
             con.logout();
             con.disconnect();
         }
         catch (Exception e)
         {
             e.printStackTrace();
         }
      }
   }
}

included libraries in Eclipse:

B4Ashared.jar
commons-net-2.2.jar
core.jar
android.jar

compiling and running in b4a:

Compiling code. 0.00
Generating R file. 0.00
Compiling generated Java code. 0.58
Convert byte code - optimized dex. 0.22
Packaging files. 0.10
Signing package file (debug key). 0.29
ZipAlign file. 0.02
Installing file to device. 6.39
Device serial: emulator-5554
Completed successfully.

Result in the emulator

see attachment.

Any ideas? I can give the source codes if needed.

Thx
 

Attachments

  • error.jpg
    error.jpg
    41.7 KB · Views: 2,202

agraham

Expert
Licensed User
Longtime User
It's probably not relevant to your problem but your version attribute is not correctly formatted. It should be

@Version(1.0f)

Your library is (correctly as it doesn't handle GUI things) a Process object (no @ActivityObject annotation) so it would be better declared in Process_Globals. Again probably not the problem here.

I'd be surprised if it is crashing on the Dim as that just calls the default constructor which from your code fragment is not declared (this is usual) and so just constructs a bare instance of your class.

The message you get is a sign of an uncaught exception. Look at the unfiltered Logcat output where hopefully you might see the exception details and a stack trace.

If you still can't crack it post the .java file and I'll try it myself.

EDIT:- I don't recognise commons-net-2.2.jar. If you do post then include that as well.
 
Last edited:

alwaysbusy

Expert
Licensed User
Longtime User
Hi agraham,

Attached are the b4a project files and the eclipse project (including the commons-net-2.2.jar). The jar makes it possible to do ftp stuff on android.

I think this is the problem. I think the error I get in logcat is that it does not find this jar. Makes sense if this jar is not copied to the emulator I guess, but I have no idea how to include it in the upload to the emulator.

The logcat is also included.

Thanks in advance for helping me to figure this one out!

PS, I made the changes you suggested and as you suspected they did not make a difference.
 

Attachments

  • logcat.zip
    217 KB · Views: 1,725
  • FTPTest.zip
    5.5 KB · Views: 1,562
  • ABFTPClient.zip
    216.4 KB · Views: 1,745

moster67

Expert
Licensed User
Longtime User
Yes, Yes.........

Yes, Yes - it worked. I created my first library for B4A. :sign0060: :sign0162:

Thank you Erel! A good guide although not everything is clear. Not many problems what regards java-code (quite similar syntax to C#) but a bit more regarding Eclipse and how to use the IDE (which is new for many of us).

Anyway, I found a good guide which helped me (and hopefully can be useful for others as well):

Eclipse IDE Tutorial

There are many advanced aspects in your tutorial as to creating libraries for B4A and hopefully in the future you can make some more mini-guides focusing different aspects. I think this will benefit all us B4A-users. Just my thoughts.

Once again my sincerest thanks for creating B4A - it's getting better for each day.
 
Last edited:

agraham

Expert
Licensed User
Longtime User
I think the error I get in logcat is that it does not find this jar.
Certainly looks like it. It's not just a case of including the jar in the final installation package. The Java bytecode in the jar needs to be translated to Dalvik bytecodes that are run by the Android virtual machine. This is done by a "Dexer" which is invoked by the IDE so the IDE will need to know about this jar in order to include it.:(

I'll have to pass this problem to Erel I think. :)
 

agraham

Expert
Licensed User
Longtime User
I initially (unsuccessfully :() tried this

I made a folder under the project in Eclipse. Did File -> Import from File System to add a jar to the folder. I then added that jar to the build path with File -> Build Path -> Libraries tab -> Add Jar.

If I reference that imported jar then it doesn't get exported for some reason so I referenced an external copy.

Then File -> Export Jar File -> Next and made sure that the src and the folder with the jar in were checked. Finish then exported the final jar with the compiled code and the imported jar.

This fails, I think because the IDE doesn't unpack the included jar to get at the class files.

I finally got it to work :) by compiling and exporting the FTP library as normal then opened both FTP.jar and commons_net-2.2.jar in separate copies of 7zip and just dragged the org folder from one to the other. Your demo then compiled and loaded fine.

Anyone know how to do this in Eclipse automatically? I can't see a way because the key seems to be that the class files need to be uncompressed in the final jar that the IDE uses.
 

alwaysbusy

Expert
Licensed User
Longtime User
Hi agraham, Erel

You are the man! :) Excellent trick that will do for the moment.

When I create a lib folder in Eclipse and add the jar file in this folder and then connect to it, I have the impression it is added in the new jar, but wrong somehow. It then adds it to to a folder lib and as a jar file. In your way it is the 'unzipped version' in org\apache\commons\net\.

Anyway, I got no more errors. I'm not able to connect to a ftp yet but that must be the java part.

Thanks a lot! Let's continue the quest :)
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
The Doclet tool was updated to v1.01.
This version supports an additional parameter named b4aignore.
This allows you to prevent the doclet from extracting information from some of the classes (similar to @Hide) based on the package name.
For example:
B4X:
-b4aignore org
This will make the doclet skip over all classes with packages startwing with org.

B4X:
-b4aignore org,com.android
Similar to the previous example, in this case also packages starting with com.android will be skipped.
 

Gigatron

Member
Licensed User
Longtime User
Hi all

4 * 8 hour of non stop library testing with Eclipse...
I managed some libraries, I managed documentation, unfortunately there are some errors at compile time with B4Android. If someone could show me with java source that will be friendly.

Also, how can we exploit. Jar files can be created with B4A a class from that file?

thank you very much

Translated with google the BIG DEMON ! :)
 

Gigatron

Member
Licensed User
Longtime User
Thank you for fast replay Agraham

I will rebuild the lib and send the errors..

Later soon
 

Gigatron

Member
Licensed User
Longtime User
Ok here is the result of my work ..


Thanks for yoyr helps


Ps: I dont know java maybe a little bit ;)
Like terms, int short string, float , for statement, globals local variables etc...
 

Attachments

  • mp3id.JPG
    mp3id.JPG
    33.8 KB · Views: 2,047

Gigatron

Member
Licensed User
Longtime User
Bonjour Agraham

Il n'y a plus rien apres javac 1.6.0_26;

Ce que je voulais faire c'est un extracteur de TAG id des fichiers mp3.

Je vais travailler pour apprendre le Java.

Merci et a bientôt.
-------
Hello Agraham

There is nothing after javac 1.6.0_26.

I would like to do a MP3 Extractor ID, i will work to learn java.

Tanks
 
Top