B4A Class DOTips 2.7 - show an overlay for app tips

Class Module: DOTips
Author: Dave O'Brien
Code Version: 2.7
Last Mod: 29 Nov 2022

Changes in this version:

- Fixed crash on showing tips for views on certain versions of Android (e.g. Android 11 Go Edition)


DOTips is a free B4A class for showing "spotlight" tips in your app.

These are commonly used for tutorials, onboarding, or showing users what's new. The tips are overlaid on your UI using a semi-transparent background, and can highlight a UI control or an arbitrary area, show a picture, or just show text.

To use, first add this class and its required libraries:
- Add this class module to your B4A project using "Add Existing Modules".
- In the Libraries Manager, make sure these internal (and free) libraries are ticked:
- JavaObject​
- StringUtils​
- Accessibility

Create a variable for the tips:
- In the activity's Sub Globals, add Dim tips As DOTips
- In the activity's Activity_Create, add tips.Initialize(Me, Activity, "tips")

To show tips, add them to the tips list and call Show:

B4X:
tips.addGeneralTip("Insert title here", "Insert description here")
tips.addPictureTip("picture.png", "Insert description here")
tips.addTipForView(someView, "Insert title here", "Insert description here")
tips.addTipForArea(someRect, someScrollview, "Insert title here", "Insert description here")
tips.show
This will show the tips in order, using the default color, text, and automatic positioning (which adjusts to the location of the view).

Note: If a view is not visible when Show is called, that tip is skipped.

To customize tips:
- Set the properties you want first. All tips you add after that will use those settings.
B4X:
tips.HighlightColor = Colors.Magenta
tips.LandscapePosition = tips.POSITION_LEFT
tips.addTipForView(someView, "Insert title here", "Insert description here")

To hide the tips when the device's Back button is tapped, add this:
B4X:
Sub activity_KeyPress(KeyCode As Int) As Boolean
   If (tips.Visible = True) And (tips.CanSkip) Then
      tips.hide(True)
      Return True
   else...

To trigger something after the tips are closed, use the OnHide event:
B4X:
sub tips_OnHide(tipsSkipped As Int)
   'do something here if desired
end sub

To resume tips after orientation changes, use GetIndexOfCurrentTip and Resume.
In the demo code, see the activity_pause and activity_resume subs.

To hide the Skip All button, set canSkip to false.

For help on the properties and methods, use the automatic tooltips in B4A or inspect the source code.

Demo code
I've attached a demo app that lets you play with all of the properties and see the tips in action.

Compatibility:
Works with B4A 4.3+. Not sure about earlier versions.
Tested on Android 4.x and later, on various phones and tablets.

Not yet implemented:
- auto-size buttons to text
- add animated transitions between tips
- change string parameters to CSBuilder
- make this a customView?

Usage:
- Creative Commons Attribution license
(You can do whatever you like with this, as long as you credit me as the creator.)

Thanks to:
- @Erel for the GetARGB code (and lots more besides)
- @klaus for the drawRoundRect code
- @thedesolatesoul for porting MSShowTips, which wasn't quite what I needed, but motivated me to give it a go myself. :)
- @stevel05 for the GetRelativeLeft and GetRelativeTop code

Questions, suggestions, bugs? Please post in this thread.

Thanks!

sample tip 2.png
sample tip 3.png
sample tip 1.png
 

Attachments

  • DOTips.bas
    55.5 KB · Views: 244
  • DOTips-demo.zip
    289.1 KB · Views: 239
Last edited:

Dave O

Well-Known Member
Licensed User
Longtime User
Hi all, I've uploaded version 1.4:
- Added maskAlpha property to control the transparency of the text/button area.
- Added nextButtonColor and nextButtonTextColor to control the appearance of the Next button.
 

jotaele

Member
Licensed User
Longtime User
Hi all, I've uploaded version 1.4:
- Added maskAlpha property to control the transparency of the text/button area.
- Added nextButtonColor and nextButtonTextColor to control the appearance of the Next button.

Amazing. Thanks. :)
 
Last edited:

Dave O

Well-Known Member
Licensed User
Longtime User
Hi all, I've uploaded version 1.5:
- Changed maskAlpha to maskColor.

If you already use maskAlpha, this will break your code (slightly).

If you don't already use maskAlpha, nothing will be affected.

This change lets you set a mask color that's completely different from the background, if you wish.

If you don't set an explicit mask color, it will default to a darker shade of the background color (as it did before).
 

Dave O

Well-Known Member
Licensed User
Longtime User
"Show" sub no visible...
plz see pic

Hmm, that's strange. Are you able to run the demo app? If so, compare that to your own code.

If you look at the DOTips class itself, you should be able to see the Show sub, and it should be defined as public.

Better yet, if you can upload your source code (or a simple version of it that shows the same problem), I can look at it today.

Thanks!
 

Dave O

Well-Known Member
Licensed User
Longtime User
Hi all, I've uploaded version 1.7:
- added OnHide event to handle optional actions after tips are closed
- text for titles and descriptions now auto-scales (but button text doesn't (yet))

Note that the Initialize method now has a few extra arguments (really easy, see the demo code).

Also note that the text auto-scaling should make the title and description text appear roughly the same relative size on all devices. (Previously, it would appear much smaller on larger displays like tablets.) Check the new text sizes on various devices and adjust as you see fit (although I think the default sizes should be good for most folks).

Thanks!
 

hibrid0

Active Member
Licensed User
Longtime User
Hi I insert the DOTips in my project, I insert this on Activity_create


B4X:
  tips.addGeneralTip("Title text", "Text text Texto.")
    tips.DoneText="Go!"
    tips.show

But not showed on activity create on first run, the tips show if I continue to the next activity and back to the activity content the tip, but I need to show it starting the app.
 
Last edited:

Dave O

Well-Known Member
Licensed User
Longtime User
In activity.create, I avoid doing GUI stuff because the views are still being drawn and strange things sometimes happen.

In my apps, if I have tips to show when the activity launches, I do that in activity_resume.

Let me know if this helps. Cheers!
 
Last edited:

hibrid0

Active Member
Licensed User
Longtime User
Hey thanks for this very nice Class.
Hi I'm here again, I solved the problem on starting the DOTips, work perfect on load.
Now I have a small problem, If I use the Tips.addTipForView and add a view work fine, but in my activities I have an Scrollview and panels, if I use tips.addTipForView (A view inside of scrollview), the class crash.

B4X:
** Service (starter) Create **
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
Error occurred on line: 751 (DOTips)
java.lang.ClassCastException: android.widget.FrameLayout$LayoutParams cannot be cast to anywheresoftware.b4a.BALayout$LayoutParams
    at anywheresoftware.b4a.objects.ViewWrapper.getLeft(ViewWrapper.java:150)
    at XXXX.XXXXX.dotips._getabsoluteposition(dotips.java:1116)
    at XXXX.XXXXX.dotips._getabsoluteposition(dotips.java:1113)
    at XXXX.XXXXX.dotips._getabsoluteposition(dotips.java:1113)
    at XXXX.XXXXX.dotips._settargetrectfromview(dotips.java:2006)
    at XXXX.XXXXX.dotips._show(dotips.java:359)
    at XXXX.XXXXX.dotips._tipsnextbutton_click(dotips.java:2188)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:697)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:339)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:246)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:134)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:153)
    at anywheresoftware.b4a.objects.ViewWrapper$1.onClick(ViewWrapper.java:78)
    at android.view.View.performClick(View.java:4204)
    at android.view.View$PerformClick.run(View.java:17355)
    at android.os.Handler.handleCallback(Handler.java:725)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:5041)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
    at dalvik.system.NativeStart.main(Native Method)
** Activity (main) Pause, UserClosed = true **
 

Dave O

Well-Known Member
Licensed User
Longtime User
Hmm, that may be because scrollviews use a different layout scheme than normal views?

I'll experiment a bit to see if I can get this working. Thanks for reporting it!
 

nwhitfield

Active Member
Licensed User
Longtime User
This is a great class; just what I needed for a final polish on one of my apps.

One suggestion, though - would it be possible to add a flag passed to the onHide sub, so I could do something like

B4X:
Sub tips_OnHide ( complete As Boolean )
If complete Then
 StateManager.SetSetting("tipsShown","yes")
 StateManager.SaveSettings
End if
End sub

Which would then mean that at the start of my app I could say something like

B4X:
If StateManager.GetSetting2("tipsShown","no") <> "yes" then tips.Show

The idea is that 'complete' would be true if and only if the user has gone through all the tips to the end. If they hide them with the Back button, for example, they'll still get the help the next time the app starts.
 

Dave O

Well-Known Member
Licensed User
Longtime User
The idea is that 'complete' would be true if and only if the user has gone through all the tips to the end. If they hide them with the Back button, for example, they'll still get the help the next time the app starts.

Good idea.

I'm thinking, though, that instead of forcing the user to view all the tips again, it might be more considerate to show them only the tips they had skipped.

To allow for both cases, the OnHide event could return the # of tips remaining (i.e. skipped) instead of a simple yes/no:
  • tipsRemaining = 0 means that the tips are complete, and you can use your logic above.
  • tipsRemaining > 0 means that, optionally, the next time you show tips, you could do some simple list adjustment (e.g. build your list and then subtract the # of tips they already saw), so that you only show them the tips they skipped.
Let me know what you think. Either way, it looks like an easy improvement for me to add.
 

Dave O

Well-Known Member
Licensed User
Longtime User
Hi all, I've uploaded version 1.8:
- OnHide event now returns # of tips skipped

As always, let me know if anything seems amiss.

Thanks!
 

Dave O

Well-Known Member
Licensed User
Longtime User
Hey thanks for this very nice Class.
Hi I'm here again, I solved the problem on starting the DOTips, work perfect on load.
Now I have a small problem, If I use the Tips.addTipForView and add a view work fine, but in my activities I have an Scrollview and panels, if I use tips.addTipForView (A view inside of scrollview), the class crash.

Hi @hibrid0 , I finally got some time to update this class and I was finally able to solve the scrollview problem you reported.

If you have a chance, please download version 2.0 of this class and let me know if it works for you.

Cheers!
 

Dave O

Well-Known Member
Licensed User
Longtime User
Hi all, I've uploaded version 2.0. This is a major update that adds:
- picture tips
- the ability to resume tips after an orientation change (requires a few lines of code - see the demo app)
- support for scrollviews
- a demo app that lets you try ALL the properties, and that shows the use of scrollviews and screen rotation.

Note: when updating, if you're using the OnHide event, check that your sub signature includes the new parameter added in v1.8. (B4A will not spot an invalid sub signature for this event at compile time, but it will crash on calling OnHide if you use the old parameter-less signature.)

As always, let me know if anything seems amiss.

Thanks!
 
Top