« MBS Xojo Conference U… | Home | MBS FileMaker Plugin,… »

Decouple two features for smaller application sizes

Let's say you make a library, a plugin or just your own set of classes. You use them in various applications, so you add them to your projects. But you don't use all parts always. If you can segment them and maybe help the Xojo compiler, it can strip out unused project items and produce smaller applications.

Use cases may be one project, which can be compiled to both the full version and a demo version. Or a Pro and a Lite version. Or a viewer vs. editor version of the application. Or simply you have a ton of utility classes, but don't need them for the current app. Then you would like to not include all the parts.

We use various of this techniques with MBS Plugin to reduce the size of your built applications. We actively avoid dependencies between plugins as much as possible.

Project Items

The Xojo compiler can strip out whole items in the project. A window that is never referenced in the current version, will be stripped. You can wrap the only reference for one part in a #if section and avoid referencing it in the smaller app version:

#if ProVersion
   Dim e as new EditorWindow
   e.show
#else
   MessageBox "Please buy Pro version."
#endif

You may define boolean constants in a module to do this. Toggle them either manually, via IDE scripting or build steps with scripts. This also helps with target constants to only include some items that are only for one platform:

#if targetWindows then
   WindowsModule.DoSomething
#elseif targetMacOS
   MacModule.DoSomething
#endif

The other module can be stripped. Same for images, classes, controls, windows and other project items.

Global Methods

Xojo can't strip methods or properties of a class and similar like a window. You may somewhere call it via Introspection or a plugin or the Xojo framework itself may use it. But Xojo can strip methods of a module. This way you can split functionality and have whole methods not be included.

Since Xojo can not strip classes, but globals, you may want to use extends keyword to add functionality to a class without forcing this code into all applications using the class:

Public Sub test(extends d as RecordInDatabase)
   MessageBox "Test called."
End Sub

This is very useful to connect two set of classes, without to link them hard together. All methods in one class calling another class could be implemented via extends to make this connection weak.

Use Variant for properties

We may have a ClassA referencing ClassB. But we don't like to have ClassB included in the application at all times. For this we define the property referencing ClassB with type Variant. This is a bit inconvenient, but the reference can be hold. Methods for ClassA referencing the other class will be implemented either as a subclass or via extends.

Let's take an example where RecordInDatabase references the database class:

Class RecordInMemory
   BackStorage as Variant
End Class

Class RecordInDatabase Inherits RecordInMemory
   Public Sub Save()
      Dim db As Database = Me.BackStorage
      ...
   End Sub
End Class

This way RecordInMemory doesn't need the reference to database, but can hold the reference. The subclass uses a local variable to access the database properties.

In MBS Plugin you often see Variant properties. The documentation then states that the variant returns an object of a certain class. Always. This is by intention and avoids other plugins with that class to be added to your application.

Avoid IsA via overwrite

The check of a class with IsA may cause an extra reference. Let's check this bad code in RecordInMemory class:

Public Sub DoSomething()
   If Self IsA RecordInDatabase Then
      Dim r As RecordInDatabase = RecordInDatabase(Self)
      r.DoSomethingWithDatabase
   End If
End Sub

This code forces any app using RecordInMemory to include RecordInDatabase, even if it never uses it. The proper way to handle this would be to overwrite a method. So the base class may define a dummy method like this:

Class RecordInMemory
Public Sub DoSomething()
  // nothing
End Sub

That method could even raise an exception to report it's not implemented and should not be used. Or we just decide to do nothing.

Now the key is that the subclass can overwrite the method and do something else:

Class RecordInDatabase
Public Sub DoSomething()
   DoSomethingWithDatabase
End Sub

This avoids the reference. Anywhere in code you can just use the base class and call DoSomething. If it is a database backed record, it would do the extra work:

Public Sub test()
   Dim r As RecordInMemory ...

   r.DoSomething
End Sub

But if you never create a RecordInDatabase object in your code, your app will not contain that class.

Extra parameters

You may define a method in the base class to take optional parameters, which only take effect in subclasses. Like if you have a class to draw objects and you have an extra parameter for PDF creation:

Public Sub SetLineWidth(ScreenLineWidth as double, PDFLineWidth as double)

The key difference is that the screen may take a value like 1.0 or 0.5, which translates on a high resolution screen to 2 or 1 pixels. For PDF we can draw much smaller lines. Since a 1 point wide line is often too much, you can pass a line width of e.g. 0.2, which looks really good on a laser printer. Or For DynaPDF you can pass 0 as line width, which may do the hairline size, which is as small as the printer can do.

Of course SetLineWidth would be define in a normal drawing function to go to a picture and just ignore the PDF line width. The subclass to draw to a PDF would take the second value. Everywhere you can call this method on the base class without checking via ISA on whether this is your new PDF drawing class or the older picture based one.

Conclusion

We have used these techniques and probably a few more to lower the size of our applications. Especially dropping a huge collection of utility classes into a project without worrying of a bloated application. If you do it right, you may drop in 20 new things, rebuild the app and the size doesn't change.

You may do more like on-demand loading of content instead of including this in your application. In one application we stopped shipping the manual PDF with the app. When you use the application, we would just download it in the background. Or when user clicks to open the manual. Then it would just open it or show the progress dialog for the download so user can wait a few seconds.

Enjoy your Xojo coding!

23 02 24 - 11:17