2
\$\begingroup\$

In my project I have a chain of Fragments where each one gets a bitmap, manipulates it, then sends it to another fragment for more processing.

My fragment chain looks like this:

CaptureFragment -> RotateFragment -> CropFragment -> ...

And here's some of the code:

abstract class BaseFragmentInOut<InVM : BitmapViewModel, OutVM : BitmapViewModel>
(private val c1: Class<InVM>, private val c2: Class<OutVM>) : BaseFragment() {
    protected lateinit var inViewModel: InVM
    protected lateinit var outViewModel: OutVM
    fun getInBitmap(): Bitmap = inViewModel.bitmap
    fun setOutBitmap(bmp: Bitmap) { outViewModel.bitmap = bmp }

    @CallSuper
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        inViewModel = getViewModel(c1)
        outViewModel = getViewModel(c2)
    }
}
sealed class BaseFragment : Fragment() {
   protected fun <T: ViewModel>getViewModel(c: Class<T>): T
            = ViewModelProviders.of(activity)[c]
}

It leads to fairly awkward code like this since we can't have reified generics in classes:

private typealias RVM = RotateViewModel
private typealias CVM = CropViewModel
class CropFragment: BaseFragmentInOut<CVM, RVM>
(CVM::class.java, RVM::class.java) {
...
}

But the alternative isn't that great either, as it leads to a lot of boilerplate code:

class CropFragment: BaseFragment() {
    private lateinit var inViewModel: RotateViewModel
    private lateinit var outViewModel: CropViewModel
    fun getInBitmap(): Bitmap = inViewModel.bitmap
    fun setOutBitmap(bmp: Bitmap) { outViewModel.bitmap = bmp }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        inViewModel = getViewModel()
        outViewModel = getViewModel()
    }
}

How good of a practice is my approach and are there other better ways to do this?

\$\endgroup\$
0
\$\begingroup\$

kotlinify getViewModel

I would change

protected fun <T: ViewModel>getViewModel(c: Class<T>): T
            = ViewModelProviders.of(activity)[c]
}
//with 
protected fun <T: ViewModel>getViewModel(c: KClass<T>): T
            = ViewModelProviders.of(activity)[c.java]
}

after replacing the Class with KClass in the activity, you can use the kotlin ones instead of the java-ones.

then you can use:

private typealias RVM = RotateViewModel
private typealias CVM = CropViewModel
class CropFragment: BaseFragmentInOut<CVM, RVM>(CVM::class, RVM::class) {
    ...
}

second example

In your second example, you tell me you must do everything in the Cropfragment. This isn't true as your Basefragment can have those fields and the parent can set them.

abstract class BaseFragmentInOut<InVM : BitmapViewModel, OutVM : BitmapViewModel : BaseFragment() {
    protected lateinit var inViewModel: InVM
    protected lateinit var outViewModel: OutVM
    fun getInBitmap(): Bitmap = inViewModel.bitmap
    fun setOutBitmap(bmp: Bitmap) { outViewModel.bitmap = bmp }

    @CallSuper
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        inViewModel = getViewModel(c1)
        outViewModel = getViewModel(c2)
    }
}

and in your CropFragment:

class CropFragment: BaseFragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        inViewModel = getViewModel()   //changes the field in BaseFragment
        outViewModel = getViewModel() //changes the field in BaseFragment
    }
}

template method

If you don't want to have onViewCreated in the basefragment or you want to de other things after having set the viewmodels in onViewCreated in the basefragment, use a template method:

create an abstract method in the super-class that should do what you want... In this case create the ViewModels

abstract class BaseFragmentInOut<InVM : BitmapViewModel, OutVM : BitmapViewModel : BaseFragment() {
    abstract fun createInViewModel() : InVM
    abstract fun createOutViewModel() : OutVM

    @CallSuper
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        inViewModel = createInViewModel()
        outViewModel = createOutViewModel()
    }
}

your sub-class is then forced to implement them:

class CropFragment: BaseFragment() {
    override fun createInViewModel() = getViewModel()
    override fun createOutViewModel() = getViewModel()
}

to much?

You can of course also combine both the template methods to one called createViewModels. Then you can create a helper-function in BaseFragmentInOut:

protected inline fun <reified I : InVM, reified O : OutVM>  initialize(){
    inViewModel = getViewModel(I::class)
    outViewModel = getViewModel(O::class)
}

The only thing you have to do at that moment is to implement createViewModels the following way:

override fun createViewModels() = initialize<RVM, CVM>()

At the moment it's not possinle to remove RVM and CVM from the initialize, but it's on the table

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.