Java Question ViewWrapper problem

airblaster

Active Member
Licensed User
Longtime User
I'm currently trying to wrap this view: range-seek-bar - A slider widget for Android allowing to set a minimum and maximum value on a numerical range. - Google Project Hosting .
So far there is the following code to wrap it:
B4X:
package de.smartshopping.rangeseekbar;

import anywheresoftware.b4a.BA;
import de.smartshopping.rangeseekbar.RangeSeekBar.OnRangeSeekBarChangeListener;
import java.lang.Number;
import anywheresoftware.b4a.BA.ActivityObject;
import anywheresoftware.b4a.BA.Author;
import anywheresoftware.b4a.BA.DependsOn;
import anywheresoftware.b4a.BA.ShortName;
import anywheresoftware.b4a.BA.Version;
import anywheresoftware.b4a.BA.Hide;
import anywheresoftware.b4a.objects.ViewWrapper;

@ShortName("RangeSeekBar2")
@Version(1.0f)
@ActivityObject
@DependsOn(values = { "rangeseekbar" })

public class RangeSeekBar2 extends ViewWrapper<RangeSeekBar<Integer>>{

   private RangeSeekBar<Integer> rangeseekbar;
   private Integer absoluteMinValue;
   private Integer absoluteMaxValue;

   public void Initialize(final BA ba, String EventName, Integer absoluteMinValue, Integer absoluteMaxValue) {
      this.absoluteMinValue = absoluteMinValue;
      this.absoluteMaxValue = absoluteMaxValue;
      super.Initialize(ba, EventName);
   }
   
   @Hide
   public void innerInitialize(final BA ba, final String EventName, boolean keepOldObject) {
      if (!keepOldObject)
         setRangeSeekBar(new RangeSeekBar<Integer>(absoluteMinValue, absoluteMaxValue, ba.context));
      super.innerInitialize(ba, EventName, true);
      if (ba.subExists(EventName + "_rangeseekbarchange")) {
         this.rangeseekbar.setOnRangeSeekBarChangeListener(new OnRangeSeekBarChangeListener<Integer>() {
            @Override
            public void onRangeSeekBarValuesChanged(RangeSeekBar<?> bar, Integer minValue, Integer maxValue) {
               ba.raiseEvent(getRangeSeekBar(), EventName + "_rangeseekbarchange", minValue, maxValue);
            }
         });
           
        }
   }
      
   @Hide
   public RangeSeekBar<Integer> getRangeSeekBar() {
      return this.rangeseekbar;
   }
   
   @Hide
   public void setRangeSeekBar(RangeSeekBar<Integer> rangeseekbar) {
      this.rangeseekbar = rangeseekbar;
   }

   public boolean getNotifyWhileDragging(){
      return this.rangeseekbar.isNotifyWhileDragging();
   }

   public void setNotifyWhileDragging(boolean arg0){
      this.rangeseekbar.setNotifyWhileDragging(arg0);
   }

   public Number getAbsoluteMinValue() {
      return this.rangeseekbar.getAbsoluteMinValue();
   }

   public Number getAbsoluteMaxValue() {
      return this.rangeseekbar.getAbsoluteMaxValue();
   }

   public Number getSelectedMinValue() {
      return this.rangeseekbar.getSelectedMinValue();
   }

   public void setSelectedMinValue(Integer arg0){
      this.rangeseekbar.setSelectedMinValue(arg0);
   }

   public Number getSelectedMaxValue() {
      return this.rangeseekbar.getSelectedMaxValue();
   }

   public void setSelectedMaxValue(Integer arg0){
      this.rangeseekbar.setSelectedMaxValue(arg0);
   }

   public void setNormalizedMinValue(double arg0){
      this.rangeseekbar.setNormalizedMinValue(arg0);
   }

   public void setNormalizedMaxValue(double arg0){
      this.rangeseekbar.setNormalizedMaxValue(arg0);
   }


}
When I'm trying to run this code, I get a RuntimeException: Object should first be initialized. This happens on the line where I call the Initialize function of the RangeSeekBar.
It turned out that the line "super.innerInitialize(ba, EventName, true);" in the Java code is where the exception triggers.

What could be the reason for this?
 

warwound

Expert
Licensed User
Longtime User
You're not making full use of the ViewWrapper class.

Your RangeSeekBar2 class does not keep a direct reference to the wrapped object, you use getObject() and setObject() to access the wrapped object.

Look at this untested modification of your original class:

B4X:
package de.smartshopping.rangeseekbar;

import anywheresoftware.b4a.BA;
import de.smartshopping.rangeseekbar.RangeSeekBar.OnRangeSeekBarChangeListener;
import java.lang.Number;
import anywheresoftware.b4a.BA.ActivityObject;
import anywheresoftware.b4a.BA.Author;
import anywheresoftware.b4a.BA.DependsOn;
import anywheresoftware.b4a.BA.ShortName;
import anywheresoftware.b4a.BA.Version;
import anywheresoftware.b4a.BA.Hide;
import anywheresoftware.b4a.objects.ViewWrapper;

@ShortName("RangeSeekBar2")
@Version(1.0f)
@ActivityObject
@DependsOn(values = { "rangeseekbar" })

public class RangeSeekBar2 extends ViewWrapper<RangeSeekBar<Integer>>{

    private Integer absoluteMinValue;
    private Integer absoluteMaxValue;

    public void Initialize(final BA ba, String EventName, Integer absoluteMinValue, Integer absoluteMaxValue) {
        this.absoluteMinValue = absoluteMinValue;
        this.absoluteMaxValue = absoluteMaxValue;
        super.Initialize(ba, EventName);
    }
    
    @Hide
    public void innerInitialize(final BA ba, final String EventName, boolean keepOldObject) {
        if (!keepOldObject){
            //   setRangeSeekBar(new RangeSeekBar<Integer>(absoluteMinValue, absoluteMaxValue, ba.context));
            setObject(new RangeSeekBar<Integer>(absoluteMinValue, absoluteMaxValue, ba.context));
        }
        super.innerInitialize(ba, EventName, true);
        if (ba.subExists(EventName + "_rangeseekbarchange")) {
            getObject().setOnRangeSeekBarChangeListener(new OnRangeSeekBarChangeListener<Integer>() {
                @Override
                public void onRangeSeekBarValuesChanged(RangeSeekBar<?> bar, Integer minValue, Integer maxValue) {
                    ba.raiseEvent(getObject(), EventName + "_rangeseekbarchange", minValue, maxValue);
                }
            });
           
        }
    }
    
    public boolean getNotifyWhileDragging(){
        return getObject().isNotifyWhileDragging();
    }

    public void setNotifyWhileDragging(boolean arg0){
        getObject().setNotifyWhileDragging(arg0);
    }

    public Number getAbsoluteMinValue() {
        return getObject().getAbsoluteMinValue();
    }

    public Number getAbsoluteMaxValue() {
        return getObject().getAbsoluteMaxValue();
    }

    public Number getSelectedMinValue() {
        return getObject().getSelectedMinValue();
    }

    public void setSelectedMinValue(Integer arg0){
        getObject().setSelectedMinValue(arg0);
    }

    public Number getSelectedMaxValue() {
        return getObject().getSelectedMaxValue();
    }

    public void setSelectedMaxValue(Integer arg0){
        getObject().setSelectedMaxValue(arg0);
    }

    public void setNormalizedMinValue(double arg0){
        getObject().setNormalizedMinValue(arg0);
    }

    public void setNormalizedMaxValue(double arg0){
        getObject().setNormalizedMaxValue(arg0);
    }
}

I've basically removed all references to the class's this.rangeseekbar member and replaced them with getObject() and setObject().

Once the ViewWrapper setObject() method is called, the object is correctly initialized.

Martin.
 

warwound

Expert
Licensed User
Longtime User
You might also only want to create and add the event listener once after the new instance of RangeSeekBar is created:

B4X:
    @Hide
    public void innerInitialize(final BA ba, final String EventName, boolean keepOldObject) {
        if (!keepOldObject){
            //    setRangeSeekBar(new RangeSeekBar<Integer>(absoluteMinValue, absoluteMaxValue, ba.context));
            setObject(new RangeSeekBar<Integer>(absoluteMinValue, absoluteMaxValue, ba.context));
            if (ba.subExists(EventName + "_rangeseekbarchange")) {
                getObject().setOnRangeSeekBarChangeListener(new OnRangeSeekBarChangeListener<Integer>() {
                    @Override
                    public void onRangeSeekBarValuesChanged(RangeSeekBar<?> bar, Integer minValue, Integer maxValue) {
                        ba.raiseEvent(getObject(), EventName + "_rangeseekbarchange", minValue, maxValue);
                    }
                });   
            }
        }
        super.innerInitialize(ba, EventName, true);
    }

Martin.
 

airblaster

Active Member
Licensed User
Longtime User
Thanks for all the hints, warwound and Erel!

@Erel: Why is it important that there are no fields in the class? I couldn't see any problems when compiling this with the fields still there.
If this is really important, is there any way to pass values from Initialize to innerInitialize when deriving from ViewWrapper?
 

airblaster

Active Member
Licensed User
Longtime User
So basically ViewWrapper classes are not suitable for Views that are initialized with parameters other than EventName?
 

Roger Garstang

Well-Known Member
Licensed User
Longtime User
Depends on how it is used. Even EventName is somewhat lost if not used in the Initialize methods to set events. In my LineLayout Class I do the no-no of having a few variables in the wrapper. Since I use it to build my screen in Create and only assign it to a Global variable it is fine. Now if I pass it to something else like a standard View object or something it gets "unwrapped" and only the ScrollView object it wraps gets passed without all my fields. So, if I try to use it again by passing that View object back into a LineLayout object none of my fields will have values.

You can store things in Tags still since that is a standard Android functionality. There is also a way to store multiple items by an index in Tags too, but it requires unique ID values to Android and I haven't messed with it enough to figure out how to get that to work.

The View you wrap can also be extended and you can add fields to it which do get passed (Works best with views that inherit or are extended from the view wrapped). You just setObject to it in the innerInitialize. To get to your extended fields and methods you just need to cast to it.

Currently I'm making a TabBar library because I hate the Tabs in Android. It extends ViewWrapper<HorizontalScrollView>. I override the normal Invalidate2, Invalidate3, SetBackgroundImage, SetLayout that seem to lose parameter names. Then I also hide the SetTag and GetTag methods and even override SetTag to do nothing in case someone gets slick. Within it I have a LinearLayout. I now have 2 Tags I can store values in and use the Scrollview's to store the Event and the LinearLayout to Store the Current Tab. My custom Button/Tab also stores Selection State and could be used to get Current Selection, but it is faster to check the Tag than Scan every Child Button of the LinearLayout. Lots of tricks can be done to get what you want/need.
 

airblaster

Active Member
Licensed User
Longtime User
Hi Roger,

thanks for you elaboration!
Since I'm not passing around the view I guess it should be fine, that's good news :)
 
Top