ViewModel, Repository and Retrofit - What goes where and how?












0















I have been experimenting Android Architecture Pattern from over 2 weeks. I have been strugling to understand what code goes where, ViewModel or Repository or somewhere else?



It really looked nice when I was learning and going through sample apps. So, I decided to use it in real life. I got stuck when I wanted to perform the login on the login button click. I couldn't understand the flow how it really should go.



The first thing was to rescue was this github issue thread, https://github.com/googlesamples/android-architecture-components/issues/63#issuecomment-310422475



Firing an event when user click on a button. All the samples and blog posts were really good pointing at loading data when the fragment starts and observing thing. But, this thing didn't worked for me. So I came out with my own solution that observes util it reaches it's end state. Like a network resource starts loading, and completes with success or error. That means there will be 2 callbacks to the observer, when the data loading starts and when it completes.



That looked something like this:



interface StatefulResource<T> {

/**
* This method return if the resource is in it's end state. All the events had occurred
* and there are no other events left to follow for them.
*
* @return true/false based if the current state is end state or not
*/
fun isEndState(): Boolean

}

class Resource<T> private constructor() : StatefulResource<T> {

var state: Int? = null
private set
var result: Result? = null

companion object {
const val STATE_LOADING = 1
const val STATE_SUCCESS = 2
const val STATE_ERROR = 3

fun <T> result(result: Result) = Resource<T>().apply { this.result = result }
fun <T> loading() = Resource<T>().apply { this.state = STATE_LOADING }
}

override fun isEndState() = (state == STATE_ERROR) or (state == STATE_SUCCESS)

fun isSuccessful() = state == STATE_SUCCESS
}

fun <J : StatefulResource<T>, T> LiveData<J>.observeStatefully(
lifecycleOwner: LifecycleOwner,
observer: Observer<J>
) {
val liveData = this
val internalObserver = object : Observer<J> {
override fun onChanged(resource: J) {
observer.onChanged(resource)
if (resource.isEndState().orFalse()) {
liveData.removeObserver(this)
}
}
}
observe(lifecycleOwner, internalObserver)
}


Now, what really happened here is how to return data from the API call. How to abstract things from the repository side and simplify the things using coroutines. I came up to this article and I made something after reading it.



I came up with something like this:



sealed class Result
data class Success<out T : Any>(val data: T?) : Result()
data class Failure(val message: String, val error: Throwable?) : Result()

interface CommonResponse<T : Any> {
var status: Int
var message: String
var data: T?

fun isValid() = status == 1

fun mapResult(): Result = if (isValid())
Success(data)
else
Failure(message, null)
}

/**
* This extension method enqueues the call using the coroutine and
* return the Result instance with Success or Failure
*/
suspend fun <T, S : CommonResponse<T>> Call<S>.getResult(): Result = try {
this.enqueueAwait().mapResult()
} catch (error: Throwable) {
Failure("Something went wrong", error)
}


Now, in the activity I have to write this code:



  viewModel.login(mBinding.editEmail.value.trim(), mBinding.editPassword.value, null)
.observeStatefully(viewLifecycleOwner, Observer { resource ->
mBinding.resource = resource

when (resource.state) {
Resource.STATE_SUCCESS -> {
navController.navigate(R.id.nav_action_fragment_sign_in_to_fragment_home)
}
}
})


I think, I have duplicated some things with this pattern. Like sealed class Result and Resource both can tell me the state of the result (Success or Failure and STATE_SUCCESS or STATE_FAILURE). So, instead of making things more clear, I made them unclear. Is there any better way? (This is certainly not the better one)



One more thing, I am finding difficult to understand is, when Login response, comes where should I write the logic of storing user information? Like in the repository I should write my logic of storing it into the prefs? Or inside the view model? What's the better way?



I couldn't find a sample that actually performs only networking with retrofit. How should I handle and architect this kind of apps?










share|improve this question



























    0















    I have been experimenting Android Architecture Pattern from over 2 weeks. I have been strugling to understand what code goes where, ViewModel or Repository or somewhere else?



    It really looked nice when I was learning and going through sample apps. So, I decided to use it in real life. I got stuck when I wanted to perform the login on the login button click. I couldn't understand the flow how it really should go.



    The first thing was to rescue was this github issue thread, https://github.com/googlesamples/android-architecture-components/issues/63#issuecomment-310422475



    Firing an event when user click on a button. All the samples and blog posts were really good pointing at loading data when the fragment starts and observing thing. But, this thing didn't worked for me. So I came out with my own solution that observes util it reaches it's end state. Like a network resource starts loading, and completes with success or error. That means there will be 2 callbacks to the observer, when the data loading starts and when it completes.



    That looked something like this:



    interface StatefulResource<T> {

    /**
    * This method return if the resource is in it's end state. All the events had occurred
    * and there are no other events left to follow for them.
    *
    * @return true/false based if the current state is end state or not
    */
    fun isEndState(): Boolean

    }

    class Resource<T> private constructor() : StatefulResource<T> {

    var state: Int? = null
    private set
    var result: Result? = null

    companion object {
    const val STATE_LOADING = 1
    const val STATE_SUCCESS = 2
    const val STATE_ERROR = 3

    fun <T> result(result: Result) = Resource<T>().apply { this.result = result }
    fun <T> loading() = Resource<T>().apply { this.state = STATE_LOADING }
    }

    override fun isEndState() = (state == STATE_ERROR) or (state == STATE_SUCCESS)

    fun isSuccessful() = state == STATE_SUCCESS
    }

    fun <J : StatefulResource<T>, T> LiveData<J>.observeStatefully(
    lifecycleOwner: LifecycleOwner,
    observer: Observer<J>
    ) {
    val liveData = this
    val internalObserver = object : Observer<J> {
    override fun onChanged(resource: J) {
    observer.onChanged(resource)
    if (resource.isEndState().orFalse()) {
    liveData.removeObserver(this)
    }
    }
    }
    observe(lifecycleOwner, internalObserver)
    }


    Now, what really happened here is how to return data from the API call. How to abstract things from the repository side and simplify the things using coroutines. I came up to this article and I made something after reading it.



    I came up with something like this:



    sealed class Result
    data class Success<out T : Any>(val data: T?) : Result()
    data class Failure(val message: String, val error: Throwable?) : Result()

    interface CommonResponse<T : Any> {
    var status: Int
    var message: String
    var data: T?

    fun isValid() = status == 1

    fun mapResult(): Result = if (isValid())
    Success(data)
    else
    Failure(message, null)
    }

    /**
    * This extension method enqueues the call using the coroutine and
    * return the Result instance with Success or Failure
    */
    suspend fun <T, S : CommonResponse<T>> Call<S>.getResult(): Result = try {
    this.enqueueAwait().mapResult()
    } catch (error: Throwable) {
    Failure("Something went wrong", error)
    }


    Now, in the activity I have to write this code:



      viewModel.login(mBinding.editEmail.value.trim(), mBinding.editPassword.value, null)
    .observeStatefully(viewLifecycleOwner, Observer { resource ->
    mBinding.resource = resource

    when (resource.state) {
    Resource.STATE_SUCCESS -> {
    navController.navigate(R.id.nav_action_fragment_sign_in_to_fragment_home)
    }
    }
    })


    I think, I have duplicated some things with this pattern. Like sealed class Result and Resource both can tell me the state of the result (Success or Failure and STATE_SUCCESS or STATE_FAILURE). So, instead of making things more clear, I made them unclear. Is there any better way? (This is certainly not the better one)



    One more thing, I am finding difficult to understand is, when Login response, comes where should I write the logic of storing user information? Like in the repository I should write my logic of storing it into the prefs? Or inside the view model? What's the better way?



    I couldn't find a sample that actually performs only networking with retrofit. How should I handle and architect this kind of apps?










    share|improve this question

























      0












      0








      0








      I have been experimenting Android Architecture Pattern from over 2 weeks. I have been strugling to understand what code goes where, ViewModel or Repository or somewhere else?



      It really looked nice when I was learning and going through sample apps. So, I decided to use it in real life. I got stuck when I wanted to perform the login on the login button click. I couldn't understand the flow how it really should go.



      The first thing was to rescue was this github issue thread, https://github.com/googlesamples/android-architecture-components/issues/63#issuecomment-310422475



      Firing an event when user click on a button. All the samples and blog posts were really good pointing at loading data when the fragment starts and observing thing. But, this thing didn't worked for me. So I came out with my own solution that observes util it reaches it's end state. Like a network resource starts loading, and completes with success or error. That means there will be 2 callbacks to the observer, when the data loading starts and when it completes.



      That looked something like this:



      interface StatefulResource<T> {

      /**
      * This method return if the resource is in it's end state. All the events had occurred
      * and there are no other events left to follow for them.
      *
      * @return true/false based if the current state is end state or not
      */
      fun isEndState(): Boolean

      }

      class Resource<T> private constructor() : StatefulResource<T> {

      var state: Int? = null
      private set
      var result: Result? = null

      companion object {
      const val STATE_LOADING = 1
      const val STATE_SUCCESS = 2
      const val STATE_ERROR = 3

      fun <T> result(result: Result) = Resource<T>().apply { this.result = result }
      fun <T> loading() = Resource<T>().apply { this.state = STATE_LOADING }
      }

      override fun isEndState() = (state == STATE_ERROR) or (state == STATE_SUCCESS)

      fun isSuccessful() = state == STATE_SUCCESS
      }

      fun <J : StatefulResource<T>, T> LiveData<J>.observeStatefully(
      lifecycleOwner: LifecycleOwner,
      observer: Observer<J>
      ) {
      val liveData = this
      val internalObserver = object : Observer<J> {
      override fun onChanged(resource: J) {
      observer.onChanged(resource)
      if (resource.isEndState().orFalse()) {
      liveData.removeObserver(this)
      }
      }
      }
      observe(lifecycleOwner, internalObserver)
      }


      Now, what really happened here is how to return data from the API call. How to abstract things from the repository side and simplify the things using coroutines. I came up to this article and I made something after reading it.



      I came up with something like this:



      sealed class Result
      data class Success<out T : Any>(val data: T?) : Result()
      data class Failure(val message: String, val error: Throwable?) : Result()

      interface CommonResponse<T : Any> {
      var status: Int
      var message: String
      var data: T?

      fun isValid() = status == 1

      fun mapResult(): Result = if (isValid())
      Success(data)
      else
      Failure(message, null)
      }

      /**
      * This extension method enqueues the call using the coroutine and
      * return the Result instance with Success or Failure
      */
      suspend fun <T, S : CommonResponse<T>> Call<S>.getResult(): Result = try {
      this.enqueueAwait().mapResult()
      } catch (error: Throwable) {
      Failure("Something went wrong", error)
      }


      Now, in the activity I have to write this code:



        viewModel.login(mBinding.editEmail.value.trim(), mBinding.editPassword.value, null)
      .observeStatefully(viewLifecycleOwner, Observer { resource ->
      mBinding.resource = resource

      when (resource.state) {
      Resource.STATE_SUCCESS -> {
      navController.navigate(R.id.nav_action_fragment_sign_in_to_fragment_home)
      }
      }
      })


      I think, I have duplicated some things with this pattern. Like sealed class Result and Resource both can tell me the state of the result (Success or Failure and STATE_SUCCESS or STATE_FAILURE). So, instead of making things more clear, I made them unclear. Is there any better way? (This is certainly not the better one)



      One more thing, I am finding difficult to understand is, when Login response, comes where should I write the logic of storing user information? Like in the repository I should write my logic of storing it into the prefs? Or inside the view model? What's the better way?



      I couldn't find a sample that actually performs only networking with retrofit. How should I handle and architect this kind of apps?










      share|improve this question














      I have been experimenting Android Architecture Pattern from over 2 weeks. I have been strugling to understand what code goes where, ViewModel or Repository or somewhere else?



      It really looked nice when I was learning and going through sample apps. So, I decided to use it in real life. I got stuck when I wanted to perform the login on the login button click. I couldn't understand the flow how it really should go.



      The first thing was to rescue was this github issue thread, https://github.com/googlesamples/android-architecture-components/issues/63#issuecomment-310422475



      Firing an event when user click on a button. All the samples and blog posts were really good pointing at loading data when the fragment starts and observing thing. But, this thing didn't worked for me. So I came out with my own solution that observes util it reaches it's end state. Like a network resource starts loading, and completes with success or error. That means there will be 2 callbacks to the observer, when the data loading starts and when it completes.



      That looked something like this:



      interface StatefulResource<T> {

      /**
      * This method return if the resource is in it's end state. All the events had occurred
      * and there are no other events left to follow for them.
      *
      * @return true/false based if the current state is end state or not
      */
      fun isEndState(): Boolean

      }

      class Resource<T> private constructor() : StatefulResource<T> {

      var state: Int? = null
      private set
      var result: Result? = null

      companion object {
      const val STATE_LOADING = 1
      const val STATE_SUCCESS = 2
      const val STATE_ERROR = 3

      fun <T> result(result: Result) = Resource<T>().apply { this.result = result }
      fun <T> loading() = Resource<T>().apply { this.state = STATE_LOADING }
      }

      override fun isEndState() = (state == STATE_ERROR) or (state == STATE_SUCCESS)

      fun isSuccessful() = state == STATE_SUCCESS
      }

      fun <J : StatefulResource<T>, T> LiveData<J>.observeStatefully(
      lifecycleOwner: LifecycleOwner,
      observer: Observer<J>
      ) {
      val liveData = this
      val internalObserver = object : Observer<J> {
      override fun onChanged(resource: J) {
      observer.onChanged(resource)
      if (resource.isEndState().orFalse()) {
      liveData.removeObserver(this)
      }
      }
      }
      observe(lifecycleOwner, internalObserver)
      }


      Now, what really happened here is how to return data from the API call. How to abstract things from the repository side and simplify the things using coroutines. I came up to this article and I made something after reading it.



      I came up with something like this:



      sealed class Result
      data class Success<out T : Any>(val data: T?) : Result()
      data class Failure(val message: String, val error: Throwable?) : Result()

      interface CommonResponse<T : Any> {
      var status: Int
      var message: String
      var data: T?

      fun isValid() = status == 1

      fun mapResult(): Result = if (isValid())
      Success(data)
      else
      Failure(message, null)
      }

      /**
      * This extension method enqueues the call using the coroutine and
      * return the Result instance with Success or Failure
      */
      suspend fun <T, S : CommonResponse<T>> Call<S>.getResult(): Result = try {
      this.enqueueAwait().mapResult()
      } catch (error: Throwable) {
      Failure("Something went wrong", error)
      }


      Now, in the activity I have to write this code:



        viewModel.login(mBinding.editEmail.value.trim(), mBinding.editPassword.value, null)
      .observeStatefully(viewLifecycleOwner, Observer { resource ->
      mBinding.resource = resource

      when (resource.state) {
      Resource.STATE_SUCCESS -> {
      navController.navigate(R.id.nav_action_fragment_sign_in_to_fragment_home)
      }
      }
      })


      I think, I have duplicated some things with this pattern. Like sealed class Result and Resource both can tell me the state of the result (Success or Failure and STATE_SUCCESS or STATE_FAILURE). So, instead of making things more clear, I made them unclear. Is there any better way? (This is certainly not the better one)



      One more thing, I am finding difficult to understand is, when Login response, comes where should I write the logic of storing user information? Like in the repository I should write my logic of storing it into the prefs? Or inside the view model? What's the better way?



      I couldn't find a sample that actually performs only networking with retrofit. How should I handle and architect this kind of apps?







      android kotlin coroutine android-architecture-components kotlinx.coroutines






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 25 '18 at 15:01









      kirtan403kirtan403

      3,57222262




      3,57222262
























          0






          active

          oldest

          votes











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53468779%2fviewmodel-repository-and-retrofit-what-goes-where-and-how%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          0






          active

          oldest

          votes








          0






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes
















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53468779%2fviewmodel-repository-and-retrofit-what-goes-where-and-how%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          404 Error Contact Form 7 ajax form submitting

          How to know if a Active Directory user can login interactively

          TypeError: fit_transform() missing 1 required positional argument: 'X'