Android Tutorial Embedding files in compiled libraries

It is possible to embed files directly in the compiled libraries by following these steps:

1. Add the attached class module, JarFileLoader, to your code (Project - Add Existing Module).
2. JarFileLoader can either load files from the Files folder or from the compiled jar.
While you develop the library you should initialize it and set it to load files from the assets folder:
B4X:
Sub Process_Globals
   Private jfl As JarFileLoader
End Sub

Sub Globals

End Sub

Sub Activity_Create(FirstTime As Boolean)
   If FirstTime Then
     jfl.Initialize(True) '<--- True = from assets folder, False = from jar file
   End If
   Activity.SetBackgroundImage(jfl.LoadBitmapFromJar("smiley.gif"))
End Sub

3. Before you compile the library you should make sure to change jfl.Initialize(True) to jfl.Initialize(False).

4. The files from the Files folder will not be included automatically in the compiled jar. You need to compile the library and then open the jar file with a program such as 7zip and copy the files to the jar.

SS-2014-02-11_09.48.36.png


The files names are case sensitive. It is recommended to use all lower case names as the IDE will automatically lower case the names when you add the files to the Files tab.
 

Attachments

  • JarFileLoader.bas
    837 bytes · Views: 831

Erel

B4X founder
Staff member
Licensed User
Longtime User
upload_2014-7-9_8-3-48.png


Add this sub to JarFileLoader:
B4X:
Sub LoadNinePatchDrawable(FileName As String) As Object
   Dim b As Bitmap = LoadBitmapFromJar(FileName)
   Dim jo As JavaObject = b
   Dim chunk() As Byte = jo.RunMethod("getNinePatchChunk", Null)
   Dim nineDrawable As JavaObject
   nineDrawable.InitializeNewInstance("android.graphics.drawable.NinePatchDrawable", _
     Array(Null, b, chunk, Null, ""))
   Return nineDrawable
End Sub

Usage example:
B4X:
Sub Activity_Create(FirstTime As Boolean)
   Dim jf As JarFileLoader
   jf.Initialize(False)
   Activity.LoadLayout("1")
   Activity.Background = jf.LoadNinePatchDrawable("l1.9.png")
   Panel1.Background = jf.LoadNinePatchDrawable("l1.9.png")
End Sub
 

NJDude

Expert
Licensed User
Longtime User
Ok, I'm doing something wrong because it's not working for me, this is what I've done:

I have a 9Patch called sample1.9.png, which is a valid 9patch and works fine if I load it using the "regular" way.

1- Ran aapt like this: aapt s -v -i sample1.9.png -o sample.9.png

2- It generated the file, no problems.

3- Added the sample.9.png to my assests via the IDE (and set the code to read the png from assets of course)

But when I run the code it fails, would you mind telling the steps you followed?, it's proven that I'm missing something.

Thank you for the code.
 
Last edited:

NJDude

Expert
Licensed User
Longtime User
No it doesn't, I'm attaching the logs.
B4X:
LogCat connected to: 015d172c9807f803
--------- beginning of /dev/log/system


Service ServiceRecord{41e614b8 u0 com.google.android.apps.maps/com.google.android.apps.gmm.prefetch.PrefetcherService} in process ProcessRecord{426a7010 1766:com.google.android.apps.maps/u0a34} not same as in map: null


Service ServiceRecord{42679d00 u0 com.google.android.music/.preferences.MusicPreferenceService$MusicPreferenceServiceBinder} in process ProcessRecord{4266d910 1989:com.google.android.music:main/u0a36} not same as in map: null
Service ServiceRecord{424dfeb8 u0 com.google.android.music/.net.NetworkMonitor} in process ProcessRecord{4266d910 1989:com.google.android.music:main/u0a36} not same as in map: null
--------- beginning of /dev/log/main


** Activity (main) Pause, UserClosed = false **


** Activity (main) Create, isFirst = true **
java.lang.NullPointerException
	at android.graphics.Rect.set(Rect.java:261)
	at android.graphics.drawable.NinePatchDrawable.getPadding(NinePatchDrawable.java:245)
	at android.view.View.setBackgroundDrawable(View.java:15357)
	at anywheresoftware.b4a.objects.ViewWrapper.setBackground(ViewWrapper.java:91)
	at njdude.generic.sample.main._activity_create(main.java:302)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:515)
	at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
	at njdude.generic.sample.main.afterFirstLayout(main.java:98)
	at njdude.generic.sample.main.access$100(main.java:16)
	at njdude.generic.sample.main$WaitForLayout.run(main.java:76)
	at android.os.Handler.handleCallback(Handler.java:733)
	at android.os.Handler.dispatchMessage(Handler.java:95)
	at android.os.Looper.loop(Looper.java:136)
	at android.app.ActivityThread.main(ActivityThread.java:5001)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:515)
	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
	at dalvik.system.NativeStart.main(Native Method)
java.lang.NullPointerException
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
** Activity (main) Create, isFirst = true **
Fatal signal 11 (SIGSEGV) at 0x00000008 (code=1), thread 7837 (.generic.sample)
** Activity (main) Pause, UserClosed = true **
Service ServiceRecord{42a81188 u0 com.google.android.apps.maps/com.google.android.apps.gmm.prefetch.PrefetcherService} in process ProcessRecord{42614eb8 7580:com.google.android.apps.maps/u0a34} not same as in map: null
** Activity (main) Create, isFirst = true **
Fatal signal 11 (SIGSEGV) at 0x00000008 (code=1), thread 8092 (.generic.sample)
** Activity (main) Create, isFirst = true **
Fatal signal 11 (SIGSEGV) at 0x00000008 (code=1), thread 8117 (.generic.sample)
** Activity (main) Create, isFirst = true **
Fatal signal 11 (SIGSEGV) at 0x00000008 (code=1), thread 8152 (.generic.sample)
** Activity (main) Create, isFirst = true **


java.lang.NullPointerException
	at android.graphics.Rect.set(Rect.java:261)
	at android.graphics.drawable.NinePatchDrawable.getPadding(NinePatchDrawable.java:245)
	at android.view.View.setBackgroundDrawable(View.java:15357)
	at anywheresoftware.b4a.objects.ViewWrapper.setBackground(ViewWrapper.java:91)
	at njdude.customtooltip.lib.customtooltip._initialize(customtooltip.java:121)
	at njdude.customtooltip.lib.main._activity_create(main.java:304)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:515)
	at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
	at njdude.customtooltip.lib.main.afterFirstLayout(main.java:98)
	at njdude.customtooltip.lib.main.access$100(main.java:16)
	at njdude.customtooltip.lib.main$WaitForLayout.run(main.java:76)
	at android.os.Handler.handleCallback(Handler.java:733)
	at android.os.Handler.dispatchMessage(Handler.java:95)
	at android.os.Looper.loop(Looper.java:136)
	at android.app.ActivityThread.main(ActivityThread.java:5001)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:515)
	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
	at dalvik.system.NativeStart.main(Native Method)
java.lang.NullPointerException
** Activity (main) Create, isFirst = true **
java.lang.NullPointerException
	at android.graphics.Rect.set(Rect.java:261)
	at android.graphics.drawable.NinePatchDrawable.getPadding(NinePatchDrawable.java:245)
	at android.view.View.setBackgroundDrawable(View.java:15357)
	at anywheresoftware.b4a.objects.ViewWrapper.setBackground(ViewWrapper.java:91)
	at njdude.customtooltip.lib.customtooltip._initialize(customtooltip.java:121)
	at njdude.customtooltip.lib.main._activity_create(main.java:304)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:515)
	at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
	at njdude.customtooltip.lib.main.afterFirstLayout(main.java:98)
	at njdude.customtooltip.lib.main.access$100(main.java:16)
	at njdude.customtooltip.lib.main$WaitForLayout.run(main.java:76)
	at android.os.Handler.handleCallback(Handler.java:733)
	at android.os.Handler.dispatchMessage(Handler.java:95)
	at android.os.Looper.loop(Looper.java:136)
	at android.app.ActivityThread.main(ActivityThread.java:5001)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:515)
	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
	at dalvik.system.NativeStart.main(Native Method)
java.lang.NullPointerException
** Activity (main) Create, isFirst = true **
java.lang.NullPointerException
	at android.graphics.Rect.set(Rect.java:261)
	at android.graphics.drawable.NinePatchDrawable.getPadding(NinePatchDrawable.java:245)
	at android.view.View.setBackgroundDrawable(View.java:15357)
	at anywheresoftware.b4a.objects.ViewWrapper.setBackground(ViewWrapper.java:91)
	at b4a.example.main._activity_create(main.java:302)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:515)
	at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
	at b4a.example.main.afterFirstLayout(main.java:98)
	at b4a.example.main.access$100(main.java:16)
	at b4a.example.main$WaitForLayout.run(main.java:76)
	at android.os.Handler.handleCallback(Handler.java:733)
	at android.os.Handler.dispatchMessage(Handler.java:95)
	at android.os.Looper.loop(Looper.java:136)
	at android.app.ActivityThread.main(ActivityThread.java:5001)
	at java.lang.reflect.Method.invokeNative(Native Method)


	at java.lang.reflect.Method.invoke(Method.java:515)
	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
	at dalvik.system.NativeStart.main(Native Method)
java.lang.NullPointerException

That's the error I keep getting with my code as well.

I tested the code on my Nexus 7 and HTC One.
 

NJDude

Expert
Licensed User
Longtime User
Ok, everything works now, however, using embedded 9patches has a caveat, it loses the margins/guides (fill and scalable area), in order to fix that, the guides for the "fill area" have to be added manually in the JarFileLoader, on this line:
B4X:
...

r.Initialize(40, 0, 85, 0) 'The values depends on the 9patch being used.

...

It seems the only 2 values needed are Left and Right, this is needed if you are adding text to the background of a view with text on it.

I'm not sure if there's a way to detect those guides automatically, but at least my solution works, a little annoying, but it works.
 
Last edited:

NJDude

Expert
Licensed User
Longtime User
That would not work, since in my case the text is dynamic, to perhaps clarify my previous post, you only need to enter the values for left and right once, the annoying part is that you will have to calculate the margins and via trial an error get the right values, it is not difficult, just annoying.
 

RandomCoder

Well-Known Member
Licensed User
Longtime User
I'm sorry but I just cannot get this to work no matter what I try (and to be honest I was being rather stupid to start with :oops:).

These are my steps in fine detail, please correct me where I am going wrong!
1). Using B4A version 4.00
2). Open the project containing only a Main module and the Class which I want to turn into a library. The Class uses one file called "crosshair.png" which is listed in the Files window.
2). Copy JarFileLoader module into project using "Project>Add Existing Module and browse to JarFileLoader.bas"
3). Declare the object in my Class Globals "Dim jfl As JarFileLoader" (I also tried declaring in Main but that didn't work because Main is excluded when compiling to a Lib)
4). Initialise in the Class Initialisation subroutine "jfl.Initialize(True)"
5). Replace the original call to load the bitmap with the new call to jfl.LoadBitmapFromJar...
B4X:
'    imgSearchCrossHair.Bitmap = LoadBitmap(File.DirAssets, "crosshair.png")
    imgSearchCrossHair.Bitmap = jfl.LoadBitmapFromJar("crosshair.png")
6). Run project in debug mode and all works perfect as before, no noticeable difference.
7). Now to compile to a Library I change the initialisation to "jfl.Initialize(False)"
8). Select compile to Library option and get a success message.
9). Open the generated .jar file using 7-zip and copy the crosshair.png file directly into the root of the folder.
10). Close folder then re-open to ensure that file is definitely still present - it is! :)
11). Open my new project which uses the Library, click Lib refresh and attempt to run in Legacy Debug (Rapid Debug doesn't show the log statement, only a Java error message). Then get the error message below stating that file was not found, Why?
Cannot find file: crosshair.png
java.lang.RuntimeException: Object should first be initialized (InputStream).
at anywheresoftware.b4a.AbsObjectWrapper.getObject(AbsObjectWrapper.java:49)
at b4a.clsColorPicker.jarfileloader._loadbitmapfromjar(jarfileloader.java:78)
at b4a.clsColorPicker.colorpicker._initcolorrangepanel(colorpicker.java:1038)
at b4a.clsColorPicker.colorpicker._initcolorpicker(colorpicker.java:914)
at b4a.clsColorPicker.colorpicker._addview(colorpicker.java:111)
at b4a.clsColorPicker.main._activity_create(main.java:390)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
at b4a.clsColorPicker.main.afterFirstLayout(main.java:98)
at b4a.clsColorPicker.main.access$100(main.java:16)
at b4a.clsColorPicker.main$WaitForLayout.run(main.java:76)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:149)
at android.app.ActivityThread.main(ActivityThread.java:5257)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:610)
at dalvik.system.NativeStart.main(Native Method)
java.lang.RuntimeException: Object should first be initialized (InputStream).

Please, any idea where I may have gone wrong?
 

RandomCoder

Well-Known Member
Licensed User
Longtime User
I've tried placing my image in a folder called assets copied from the Compiled APK test project but still no joy. Anyone any idea what I might be doing wrong?
 

RandomCoder

Well-Known Member
Licensed User
Longtime User
Can you upload the compiled jar file?
I've attached the file and I have also had another stab at it this morning in the vain hope that a fresh mind might solve the problem. I've started by deleting the Jar and XML library files and un-installed the test Apps from my device then rebooted. It seems that I now have a new error but I'm still non the wiser as the project containing the Class with which I create the Library is still functioning normally. Here's the new error logs...

Error message in legacy debug and when released mode....
** Activity (main) Create, isFirst = true **

java.lang.NoSuchMethodError: No virtual method Initialize2(IIII)V in class Lanywheresoftware/b4a/objects/drawable/ColorDrawable; or its super classes (declaration of 'anywheresoftware.b4a.objects.drawable.ColorDrawable' appears in /data/app/b4a.example-1/base.apk)

at b4a.clsColorPicker.colorpicker._getbuttoncolordrawable(colorpicker.java:325)
at b4a.clsColorPicker.colorpicker._initrbg_updowncontrols(colorpicker.java:743)
at b4a.clsColorPicker.colorpicker._initcontrolspanel(colorpicker.java:663)
at b4a.clsColorPicker.colorpicker._initcolorpicker(colorpicker.java:555)
at b4a.clsColorPicker.colorpicker._designercreateview(colorpicker.java:294)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
at anywheresoftware.b4a.objects.CustomViewWrapper.AfterDesignerScript(CustomViewWrapper.java:57)
at anywheresoftware.b4a.keywords.LayoutBuilder.loadLayout(LayoutBuilder.java:155)


at anywheresoftware.b4a.objects.ActivityWrapper.LoadLayout(ActivityWrapper.java:208)
at b4a.example.main._loadviews(main.java:370)
at b4a.example.main._activity_create(main.java:303)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:175)
at b4a.example.main.afterFirstLayout(main.java:98)
at b4a.example.main.access$100(main.java:16)
at b4a.example.main$WaitForLayout.run(main.java:76)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

--------- beginning of crash

Error message in Rapid Debug mode...
** Activity (main) Create, isFirst = true **

** Activity (main) Resume **

An error occurred:

(Line: 44) svControls.Panel.LoadLayout("lyt_Controls")
java.lang.ClassCastException: java.lang.Byte cannot be cast to java.lang.String

I'd like to understand what I'm doing wrong as this is the first Library I've created :oops:
Below is the code at which it is now saying that the error occurs, I know I'm doing something wrong but have no idea what and I've wasted a couple of days trying to solve it myself.

B4X:
'call to getButtonColorDrawable...
btnRGB_Down(intIndex).Background = getButtonColorDrawable

'(where btnRGB is declared as an array of 3 buttons.)


Private Sub getButtonColorDrawable() As StateListDrawable
   ' Define GradientDrawable for Enabled state
    Dim cdEnabled As ColorDrawable
    cdEnabled.initialize2(Colors.DarkGray, 4dip, 1dip, Colors.LightGray)

   ' Define GradientDrawable for Pressed state
    Dim cdPressed As ColorDrawable
    cdPressed.initialize2(Colors.LightGray, 4dip, 1dip, Colors.Yellow)

   ' Define StateListDrawable
   Dim stdGradient As StateListDrawable
   stdGradient.Initialize
   stdGradient.AddState2(Array As Int(stdGradient.State_enabled, -stdGradient.State_Pressed), cdEnabled)
   stdGradient.AddState(stdGradient.State_Pressed, cdPressed)
   Return stdGradient
End Sub
 

Attachments

  • JarFile.zip
    19.7 KB · Views: 300

RandomCoder

Well-Known Member
Licensed User
Longtime User
Try to install B4A in a new folder. Seems like you are referencing the wrong version of the core library.
Thank you for taking the time to look at this @Erel, I'd have never found that myself!!!
I'm at work at the moment but will try when I get home. I plan on releasing the lib as donationware and anyone that donates will be sent the original Class module so that they can make modifications as they please, that's why I'm using an older version of B4A as not everyone keeps up with the latest and greatest ;)
 

RandomCoder

Well-Known Member
Licensed User
Longtime User
@Erel thank you so much. I've updated B4A to the latest version and used this to compile the library and run the demo app which I'm currently working on. I've had to make a few changes to the Class to get it to work error free, but these were errors that I was able to make sense of :D. I've also tested the new library with version 4.0 and it works with that too. Are all libraries backwards compatible with the different versions of B4A?
 
Top