I want to use MVVM architecture, I have a view pager which has two fragments. each fragment has a RecyclerView. I created a view model class that extends AndroidViewModel because I needed context to pass to my repository which gets data from an API class that gets data from the server using volley. I expected when a rotate my phone, my ViewModel class doesn't be recalled but based on the logs which I set, it seems when I rotate the phone everything will be starting again.
is it ok? or I have something wrong?
this is my Fragment codes:
val viewModel: Fragment1ViewModel =
ViewModelProviders.of(this).get(Fragment1ViewModel(Application())::class.java)
viewModel.getData().observe(this, Observer {
Log.i("Log","data is Loaded in activity")
})
this is ViewModel Class codes:
class Fragment1ViewModel(application: Application) : AndroidViewModel(application) {
private val repository=Repository(application)
fun getData():LiveData<String>{
Log.i("Log","get data in view model")
return repository.getListItem()
}
}
this is my repository codes:
class Repository(private val context: Context) {
companion object {
private lateinit var serverFetch: ServerFetch
}
private fun setContext() {
serverFetch = ServerFetch(context)
}
fun getListItem():LiveData<String>{
setContext()
return serverFetch.getData()
}
}
and this is my ServerFetch class which works as an API for repository:
class ServerFetch(private val context: Context) {
private val api: String = "https://jsonplaceholder.typicode.com/posts"
private val result = MutableLiveData<String>()
private fun connect() {
val request = StringRequest(Request.Method.GET, api, Response.Listener {
Log.i("Log", "ServerFetch Successfully")
result.value = it
}, Response.ErrorListener {
Log.i("Log", "ServerFetch field: ${it.toString()}")
})
val queue = Volley.newRequestQueue(context)
queue.add(request)
}
fun getData(): LiveData<String> {
connect()
return result
}
}
and here is my Logs when app will run for the first time:
I/Log: get data in view model
I/Log: ServerFetch Successfully
I/Log: data is Loaded in activity
and this is my logs when I rotate my phone
I/Log: get data in view model
I/Log: ServerFetch Successfully
I/Log: data is Loaded in activity
You should fetch it at the instantiation of the view model.
ViewModel:
class Fragment1ViewModel(application: Application) : AndroidViewModel(application) {
private val repository = Repository(application)
val data = repository.getListItem()
}
Fragment:
viewModel.data.observe(this, Observer {
Log.i("Log","data is Loaded in activity")
})
Related
The code A collect Flow in ViewModel, and convert a Flow as hot Flow using MutableStateFlow, then use collectAsState() in Compose.
The code B collect Flow in Compose, and use collectAsState in Compose directly.
Which one is the better between code A and code B ?
In my mind, the code A convert a Flow as hot Flow in ViewModel, it will retain memeory and waster resources, is it a good way?
Code A
#Composable
fun BookListScreen(
viewModel: BookListViewModel,
...
) {
val booksListUiState by viewModel.uiState.collectAsState()
...
}
#HiltViewModel
class BookListViewModel #Inject constructor(
private val bookUseCase: BookUseCase
) : ViewModel() {
private var loadBooksJob: Job? = null
private val _uiState = MutableStateFlow(BookListUiState())
val uiState = _uiState.asStateFlow()
init {
loadBooks()
}
fun loadBooks() {
loadBooksJob?.cancel()
loadBooksJob = viewModelScope.launch {
bookUseCase.listBooks().collect { resultState ->
_uiState.update {
it.copy(bookListState = resultState)
}
}
}
}
...
}
override fun loadBooks(): Flow<ResultState<List<Book>>> {
...
}
Code B
#Composable
fun Greeting(
name: String,
mViewMode:SoundViewModel= viewModel()
) {
Column(
) {
val myResult by mViewMode.listRecord().collectAsState(initial =Result.Error(Exception()) )
..
}
#HiltViewModel
class SoundViewModel #Inject constructor(
private val aSoundMeter: RecordRepository
): ViewModel()
{
fun listRecord(): Flow<Result<List<MRecord>>> {
return aSoundMeter.listRecord()
}
}
#Dao
interface RecordDao {
#Query("SELECT * FROM record_table ORDER BY createdDate desc")
fun listRecord(): Flow<List<RecordEntity>>
}
class RecordRepository #Inject constructor(private val mRecordDao:RecordDao): IRecordRepository {
override fun listRecord(): Flow<Result<List<MRecord>>> {
...
}
}
In Code A, you're keeping a flow into the view model. Therefore, this flow will be saved in case of the user rotates the device. Also, the loadBooks will not be called in case of a recomposition in BookListScreen.
In Code B, listRecord is called every time a recomposition happens, therefore every time the screen is redrawn you're doing a data base call. Since your DB is local, the damage does not look so bad, but if you're using a remote DB (like Firebase or REST web service) this code will perform a network call in every recomposition, including when you rotate the device or returning to this screen.
Check it out this Playlist from Android Developers Channel. In particular this video (which is more focused in your problem).
I'm facing this issue where the data I'm retrieving from an API, https://randomuser.me/api/ at first compose it doesn't load.
But every time I rotate the screen the data updates.
First load
After screen rotation
View
class MainActivity : ComponentActivity() {
private val userViewModel : UserViewModel by viewModels()
private var userList: List<UserModel> = listOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
userViewModel.userModel.observe(this, Observer {
userList = it
})
userViewModel.onCreate()
setContent {
ListUsers(userList = userList)
}
}
}
ViewModel
class UserViewModel : ViewModel() {
val userModel = MutableLiveData<List<UserModel>>()
var getRandomUsersUseCase = RandomUsersUseCase()
fun onCreate() {
viewModelScope.launch {
val result = getRandomUsersUseCase()
if(!result.isNullOrEmpty()){
userModel.postValue(result)
}
}
}
}
Use State to ensure the data changes trigger recomposition of the Composable.
If you use another observable type such as LiveData in Compose, you
should convert it to State before reading it in a composable using
a composable extension function like LiveData.observeAsState().
Changes to your code would be,
val userListState by userViewModel.userModel.observeAsState()
setContent {
ListUsers(userList = userListState)
}
Why does it shows the data during rotation?
When rotating the screen or during any other configuration changes, the activity will be recreated.
More info on that here - Docs
In most cases, you would not require data to be changed when the screen rotates.
If you want to persist the data even after screen rotation, move the code inside onCreate() in your UserViewModel to the init block, like this.
init {
getData()
}
fun getData() {
viewModelScope.launch {
val result = getRandomUsersUseCase()
if(!result.isNullOrEmpty()){
userModel.postValue(result)
}
}
}
If you need to refresh the data on any other event like button click, swipe to refresh, etc, just call the getData() again on the event handler.
P.S: Check correct imports are added as required.
import androidx.compose.runtime.setValue
import androidx.compose.runtime.getValue
We have been discussing about this but we don't know the reason of creating a viewmodel factory to create a viewmodel instead of instantiate the viewmodel directly. What is the gain of creating a factory that just creates the viewmodel?
I just put a simple example of how I did it without Factory
here is the kodein module:
val heroesRepositoryModel = Kodein {
bind<HeroesRepository>() with singleton {
HeroesRepository()
}
bind<ApiDataSource>() with singleton {
DataModule.create()
}
bind<MainViewModel>() with provider {
MainViewModel()
}
}
The piece of the Activity where I instantiate the viewmodel without using the factory
class MainActivity : AppCompatActivity() {
private lateinit var heroesAdapter: HeroAdapter
private lateinit var viewModel: MainViewModel
private val heroesList = mutableListOf<Heroes.MapHero>()
private var page = 0
private var progressBarUpdated = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this)
.get(MainViewModel::class.java)
initAdapter()
initObserver()
findHeroes()
}
The ViewModel where I instantiate the usecase directly without having it in the constructor
class MainViewModel : ViewModel(), CoroutineScope {
private val heroesRepository: HeroesRepository = heroesRepositoryModel.instance()
val data = MutableLiveData<List<Heroes.MapHero>>()
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = uiContext + job
fun getHeroesFromRepository(page: Int) {
launch {
try {
val response = heroesRepository.getHeroes(page).await()
data.value = response.data.results.map { it.convertToMapHero() }
} catch (e: HttpException) {
data.value = null
} catch (e: Throwable) {
data.value = null
}
}
}
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
So here a example using factory
class ListFragment : Fragment(), KodeinAware, ContactsAdapter.OnContactListener {
override val kodein by closestKodein()
private lateinit var adapterContacts: ContactsAdapter
private val mainViewModelFactory: MainViewModelFactory by instance()
private val mainViewModel: MainViewModel by lazy {
activity?.run {
ViewModelProviders.of(this, mainViewModelFactory)
.get(MainViewModel::class.java)
} ?: throw Exception("Invalid Activity")
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_list, container, false)
}
The viewmodelfactory:
class MainViewModelFactory (private val getContacts: GetContacts) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(getContacts) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
And the viewmodel:
class MainViewModel(private val getContacts: GetContacts) : BaseViewModel() {
lateinit var gamesList: LiveData<PagedList<Contact>>
var contactsSelectedData: MutableLiveData<List<Contact>> = MutableLiveData()
var contactsSelected: ArrayList<Contact> = ArrayList()
private val pagedListConfig by lazy {
PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(PAGES_CONTACTS_SIZE)
.setPageSize(PAGES_CONTACTS_SIZE)
.setPrefetchDistance(PAGES_CONTACTS_SIZE*2)
.build()
}
Here is the complete first example:
https://github.com/ibanarriolaIT/Marvel/tree/mvvm
And the complete second example:
https://github.com/AdrianMeizoso/Payment-App
We can not create ViewModel on our own. We need ViewModelProviders utility provided by Android to create ViewModels.
But ViewModelProviders can only instantiate ViewModels with no arg constructor.
So if I have a ViewModel with multiple arguments, then I need to use a Factory that I can pass to ViewModelProviders to use when an instance of MyViewModel is required.
For example -
public class MyViewModel extends ViewModel {
private final MyRepo myrepo;
public MyViewModel(MyRepo myrepo) {
this.myrepo = myrepo;
}
}
To instantiate this ViewModel, I need to have a factory which ViewModelProviders can use to create its instance.
ViewModelProviders Utility can not create instance of a ViewModel with argument constructor because it does not know how and what objects to pass in the constructor.
In short,
if we need to pass some input data to the constructor of the viewModel , we need to create a factory class for viewModel.
Like example :-
class MyViewModelFactory constructor(private val repository: DataRepository): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(MyViewModel::class.java!!)) {
MyViewModel(this.repository) as T
} else {
throw IllegalArgumentException("ViewModel Not Found")
}
}
}
Reason
We cannot directly create the object of the ViewModel as it would not be aware of the lifecyclerOwner. So we use :-
ViewModelProviders.of(this, MyViewModelFactory(repository)).get(MyViewModel::class.java)
We have been discussing about this but we don't know the reason of creating a viewmodel factory to create a viewmodel instead of instantiate the viewmodel directly. What is the gain of creating a factory that just creates the viewmodel?
Because Android will only give you a new instance if it's not yet created for that specific given ViewModelStoreOwner.
Let's also not forget that ViewModels are kept alive across configuration changes, so if you rotate the phone, you're not supposed to create a new ViewModel.
If you are going back to a previous Activity and you re-open this Activity, then the previous ViewModel should receive onCleared() and the new Activity should have a new ViewModel.
Unless you're doing that yourself, you should probably just trust the ViewModelProviders.Factory to do its job.
(And you need the factory because you typically don't just have a no-arg constructor, your ViewModel has constructor arguments, and the ViewModelProvider must know how to fill out the constructor arguments when you're using a non-default constructor).
When we are simply using ViewModel, we cannot pass arguments to that ViewModel
class GameViewModel() : ViewModel() {
init {
Log.d(TAG, "GameViewModel created")
}
}
However, in some cases, we need to pass our own arguments to ViewModel. This can be done using ViewModelFactory.
class ScoreViewModel(finalScore: Int) : ViewModel() {
val score = finalScore
init {
Log.d(TAG, "Final score: $finalScore")
}
}
And to instantiate this ViewModel, we need a ViewModelProvider.Factory as simple ViewModel cannot instantiate it.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
When it comes to instantiating object of this ViewModel i.e with ViewModelProvider, we pass ViewModelFactory as an argument which contains information about our custom arguments which we want to pass. It goes like:
viewModelFactory = ScoreViewModelFactory(score)
viewModel = ViewModelProvider(this,viewModelFactory).get(ScoreViewModel::class.java)
That is why factory methods are there.
i'm trying to create a component that stream data from remote service continuously. The component starts and stops according to spring container lifecycle. I'm not sure how to test this component as the subscription is done inside my component so i was wondering wether this is the correct way to implement this kind of component with webflux or not. Does anybody know any similar component in any framework from where i might take some ideas?
Regards
class StreamingTaskAdapter(
private val streamEventsUseCase: StreamEventsUseCase,
private val subscriptionProperties: subscriptionProperties,
) : SmartLifecycle, DisposableBean, BeanNameAware {
private lateinit var disposable: Disposable
private var running: Boolean = false
private var beanName: String = "StreamingTaskAdapter"
private val logger = KotlinLogging.logger {}
override fun start() {
logger.info { "Starting container with name $beanName" }
running = true
doStart()
}
private fun doStart() {
disposable = Mono.just(
CreateSubscriptionCommand(
subscriptionProperties.events,
subscriptionProperties.owningApplication
)
)
.flatMap(streamEventsUseCase::createSubscription)
.flatMap { subscription ->
Mono.just(subscription)
.map(::ConsumeSubscriptionCommand)
.flatMap(streamEventsUseCase::consumeSubscription)
}
.repeat()
.retryWhen(Retry.backoff(MAX_ATTEMPTS, Duration.ofSeconds(2)).jitter(0.75))
.doOnSubscribe { logger.info { "Started event streaming" } }
.doOnTerminate { logger.info { "Stopped event streaming" } }
.subscribe()
}
override fun stop() {
logger.info("Stopping container with name $beanName")
doStop()
}
override fun isRunning(): Boolean = running
private fun doStop() {
running = false
disposable.dispose()
}
override fun destroy() {
logger.info("Destroying container with name $beanName")
doStop()
}
override fun setBeanName(name: String) {
this.beanName = name
}
companion object {
const val MAX_ATTEMPTS: Long = 3
}
}
I want to get input from the user using EditText and pass it to server and show the response to the user. I do this simply without any architecture but I would like to implement it in MVVM.
this is my repository code:
class Repository {
fun getData(context: Context, word: String): LiveData<String> {
val result = MutableLiveData<String>()
val request = object : StringRequest(
Method.POST,
"https://jsonplaceholder.typicode.com/posts",
Response.Listener {
result.value = it.toString()
},
Response.ErrorListener {
result.value = it.toString()
})
{
#Throws(AuthFailureError::class)
override fun getParams(): MutableMap<String, String> {
val params = HashMap<String, String>()
params["word"] = word
return params
}
}
val queue = Volley.newRequestQueue(context)
queue.add(request)
return result
}
}
and these are my View Model codes:
class ViewModel(application: Application) : AndroidViewModel(application) {
fun getData(word: String): LiveData<String> {
val repository = Repository()
return repository.getData(getApplication(), word)
}
}
and my mainActivity would be like this:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val model = ViewModelProviders.of(this).get(ViewModel::class.java)
model.getData("test").observe(this, Observer {
Log.i("Log", "activity $it")
})
}
}
My layout has an EditText which I want to get user input and pass it to the server, how should i do that?
Here how i did it in my projet.
You can probably use android annotations.
It's gonna requiere you to put some dependencies and maybe change the class a bit, but then you gonna link your Viewmodel with your repository and then you gonna have to program the setter of the variable to do a notifyChange() by making the class herited from BaseObservable. Then in the xml, if you did the correctly, you should be able to do a thing like text:"#={model.variable}" and it should be updating at the same time.
A bit hard and explain or to show for me sorry, but i would look into Android Annotations with #DataBinding, #DataBound :BaseObservable
https://github.com/androidannotations/androidannotations/wiki/Data-binding-support
Hope that can help a bit!