3

I implemented a Fragment that initially implemented android.arch.lifecycle.Observer and I wanted to start observing some other live data but can't. I started with this:

class MyFragment : BaseFragment(), Observer<FragmentData> {
  lateinit var viewModel: MyViewModel

  override fun onActivityCreated(savedInstanceState: Bundle?) {
    viewModel.fragmentData.observe(this, this)
  }

  override fun onChanged(data: FragmentData?) {
    activity?.title = getTitleFromData(data)
  }
}

If I update the class to include the other observable data like this:

class MyFragment : BaseFragment(), Observer<FragmentData>, Observer<OtherData> {
  lateinit var viewModel: MyViewModel

  override fun onActivityCreated(savedInstanceState: Bundle?) {
    viewModel.fragmentData.observe(this, this)
  }

  override fun onChanged(otherData: OtherData) {
    // update UI from otherData
  }

  override fun onChanged(data: FragmentData?) {
    activity?.title = getTitleFromData(data)
  }
}

I get an error:

Type parameter T of 'Observer' has inconsistent values: FragmentData, OtherData A supertype appears twice

I would like some help understanding why the compiler isn't able to discern the difference between the types and wondered the best alternative pattern.

something like this?:

viewModel.fragmentData.observe(this, fragmentDataObserver)

private val fragmentDataObserver = Observer<Fragmentdata> {
   activity?.title = getTitleFromData(it)
}
Jeff
  • 2,198
  • 3
  • 21
  • 38
  • You should be able to just use a lambda for your `Observer`. – CommonsWare Jul 07 '18 at 20:35
  • There is a fair amount of work that is done when the data changes for those 2 Observables that I want compartmentalized. I don't like the idea of cramming that all in the onActivityCreated() – Jeff Jul 07 '18 at 20:37
  • 1
    So, have your lambda call another function that does the bulk of the work. Or use a function reference: https://stackoverflow.com/a/32600222/115145. Your attempt to implement an interface twice using separate generics fails in Java as well: `public class Scrap implements Observer, Observer` results in a "duplicate class" syntax error. – CommonsWare Jul 07 '18 at 20:48

2 Answers2

1

You can try yo implement a generic Observer interface like this:

class MyFragment : BaseFragment(), Observer<Any> {...} 

and then on the onChanged method use

override fun onChanged(any: Any?) {
    when:
    any is isOtherData -> Do OtherData things
    any is FragmentData -> Do FragmentData things
}

A second workaround is to make a father class of the OtherData and FragmentData, FatherInterface is just an interface with nothing in the body:

interface OtherData: FatherInterface{}...
interface FragmentData : FatherInterface{}...

Then you can do

class MyFragment : BaseFragment(), Observer<FatherInterface>{}...

override fun onChanged(fatherInterface: FatherInterface?) {
    when{
        fatherInterface is OtherData -> ...
        fatherInterface is FragmentData -> ...
    }
    // update UI from otherData
}
Erick
  • 270
  • 3
  • 5
0

The compiler isn't able to discern the difference between the types because of type erasure. When your program compiles, virtually all generic information is stripped from the classes. There is no longer Observer<FragmentData> and Observer<OtherData>, there is just a single Observer class. Now, without type erasure, it would make sense for things like this to be possible

class MyFragment : BaseFragment(), Observer<FragmentData>, Observer<OtherData> {
   override fun onChanged(otherData: OtherData) {
      // update UI from otherData
    }

    override fun onChanged(data: FragmentData?) {
      activity?.title = getTitleFromData(data)
    }
}

but once you apply type erasure, then you are basically trying extend the same class twice and provide two different override methods for the same base method, which is not allowed. So that's why the compiler won't let you do it. That's as accurate a description as I can give with my limited knowledge.

As for an alternative, like the comments suggested, try using a lambda instead of trying to make the fragment itself a dual observer.

class MyFragment : BaseFragment(), Observer<OtherData> {
    lateinit var viewModel: MyViewModel

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel.fragmentData.observe(this, Observer { onChanged(it) })
    }

    override fun onChanged(otherData: OtherData?) {
        // update UI from otherData
    }

    private fun onChanged(data: FragmentData?) {
        activity?.title = getTitleFromData(data)
    }
}
Leo Aso
  • 11,898
  • 3
  • 25
  • 46