ViewModel, Repository and Retrofit - What goes where and how?
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
add a comment |
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
add a comment |
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
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
android kotlin coroutine android-architecture-components kotlinx.coroutines
asked Nov 25 '18 at 15:01
kirtan403kirtan403
3,57222262
3,57222262
add a comment |
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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