Can put solution of first challenge?

Can put solution of first challenge ?

This worked for me: there is a new view model class

fun bbRate(r: Int): Float =
    if (0 <= r || r <= 100) 0.5f + ((r.toFloat() / 100.0f ) * 1.5f)
    else throw IllegalArgumentException("rate out of bound: $r")

class BeatBoxViewModel(private val beatBox: AtomicReference<BeatBox>): BaseObservable() {

    private val TAG: String? = this::class.simpleName
    val max = 100
    private val default = 33

    private var _rate: Int = default

    init {
        beatBox.get()?.apply { rate = bbRate(default) }
        Log.d(TAG, "beatbox default rate is ${bbRate(default)}")
    }
    
    @get:Bindable
    @set:Bindable
    var rate: Int
        get() = _rate
        set(value) {
            _rate = value
            beatBox.get()?.apply { rate = bbRate(_rate) }
            Log.d(TAG, "beatbox rate is now $_rate")
            notifyChange()
        }
}

some changes to the xml for the main layout:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="viewModel"
            type="com.xrpn.bnr.beatbox.viewmodel.BeatBoxViewModel" />
    </data>
    <LinearLayout
        android:id="@+id/sound_view_layout"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_grid_view"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="90">
        </androidx.recyclerview.widget.RecyclerView>

        <SeekBar
            android:id="@+id/seekbar_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="10"
            android:max="@{viewModel.max}"
<!-- this is a two-way (read/write) binding -->
            android:progress="@={viewModel.rate}">
        </SeekBar>

    </LinearLayout>
</layout>

and a little new code in MainActivity:

        val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.viewModel = BeatBoxViewModel(beatBox)

Also, BeatBox has a new attribute:

class BeatBox(private val assets: AssetManager) {
    ...
    private var _rate: Float = 1.0f

    var rate: Float
        get() = _rate
        set(value) {
            _rate = when {
                value < 0.5f -> 0.5f
                2.0f < value -> 2.0f
                else -> value
            }
        }
    ...
    fun play(sound: SoundWav) {
        sound.soundId?.let {
            // used here                                                                                                                vvvv
            soundPool.play(it, 1.0f, 1.0f, DEFAULT_SOUND_PRIORITY, NO_SOUND_LOOP, rate)
        }
    }

thanks to the posters at

this is an attempt to return the favor :wink:

1 Like

Here there is my solution, I smashed my head on the keyboard because I forgot to bind the view model to Main activity (I only bound it to the viewholders as the book did). I tried to figure out the onProgressChange of the SeekBar directly in the view model and I went crazy x2 (that “solution” is still not working for me).

But after I figured out that simple error this challenge is doable, there is my code

<LinearLayout
        android:orientation="vertical"
        android:id="@+id/bottom_view_group"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        android:padding="10dp">

        <TextView
            android:id="@+id/rate_label"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toTopOf="@+id/seek_bar"
            tools:text="PlayBack Speed 0%"
            android:text="@{`PlayBack Speed `+(viewModel.seekBarValue+50)+`%`}"/>

        <SeekBar
            android:id="@+id/seek_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            android:max="150"
            android:progress="@={viewModel.seekBarValue}"/>

    </LinearLayout>

The key is the double-way binding on the progress attribute, so you can use it not only to set a default value defining where to start, but you can interact with it.

class SoundViewModel(private val beatBox: BeatBox): BaseObservable() {

var sound: Sound? = null
    set(sound) {
        field = sound
        notifyChange()
    }

@set:Bindable
var rate: Int? = 0
    set(value) {
        field = value
        value?.let {
            BeatBox.soundRate = it/100f
        }

    }

@get:Bindable
val title: String?
    get() = sound?.name

fun onButtonClicked() {
    sound?.let {
        beatBox.play(it)
    }
}

@Bindable
var seekBarValue: Int = 50
    set(value) {
        field = value
        rate = value+50
        notifyPropertyChanged(BR.seekBarValue)
    }

here the mainActivity code

    beatBox = BeatBox(assets)
    val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    binding.viewModel = SoundViewModel(beatBox) // here we put the view model for the R.layout.activity_main

and here the BeatBox (I put a static variable that represents the rate)

fun play(sound: Sound) {
    sound.soundId?.let {
        soundPool.play(it, 1f, 1f, 1, 0, soundRate)
    }
}

companion object{
    var soundRate = 1f
}