architecture-samples | A collection of samples to discuss and showcase different architectural tools and patterns for Andro | Architecture library
kandi X-RAY | architecture-samples Summary
Support
Quality
Security
License
Reuse
Currently covering the most popular Java, JavaScript and Python libraries. See a Sample Here
architecture-samples Key Features
architecture-samples Examples and Code Snippets
Trending Discussions on architecture-samples
Trending Discussions on architecture-samples
QUESTION
The Code A is from the official sample project.
Result
is a sealed class, I'm very strange why Loading
is defined as object class.
I think the Code B is more reasonable, is it right?
Code A
sealed class Result {
data class Success(val data: T) : Result()
data class Error(val exception: Exception) : Result()
object Loading : Result()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
Loading -> "Loading"
}
}
}
Code B
sealed class Result {
data class Success(val data: T) : Result()
data class Error(val exception: Exception) : Result()
data class Loading : Result()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
is Loading -> "Loading"
}
}
}
Added Content:
To Tenfour04 and a_local_nobody: Thanks!
The Android Studio can compile and run after I add is
before Loading -> "Loading"
in Code C.
1: What are differents between Code A and Code C?
2: And more, In my mind, all types whithin sealed class should be the same, maybe they are all data class, or they are all object. But Code A mix data class and object, and it's Ok, does it mean that I add even Interface
whithin sealed class ?
Code C
sealed class Result {
data class Success(val data: T) : Result()
data class Error(val exception: Exception) : Result()
object Loading : Result()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
is Loading -> "Loading" // I add "is"
}
}
}
ANSWER
Answered 2022-Mar-24 at 11:20that won't work, because:
data class Loading : Result() <-- this isn't valid for a data class
Data classes must have at least one primary constructor parameter, presumably the author used an object there to avoid having to make use of a constructor value, compared to the others:
data class Success(val data: T) : Result() <-- (val data: T)
data class Error(val exception: Exception) : Result() <-- (val exception: Exception)
which clearly require values
QUESTION
Freshly installing the app, the view model doesn't bind the data. Closing the app and opening it again shows the data on the screen.
Is there any problem with the pre-population of data or is the use of coroutine is not correct?
If I use Flow in place of LiveData, it collects the data on the go and works completely fine, but its a bit slow as it is emitting data in the stream.
Also, for testing, The data didn't load either LiveData/Flow. Tried adding the EspressoIdlingResource
and IdlingResourcesForDataBinding
as given here
@Provides
@Singleton
fun provideAppDatabase(
@ApplicationContext context: Context,
callback: AppDatabaseCallback
): AppDatabase {
return Room
.databaseBuilder(context, AppDatabase::class.java, "database_name")
.addCallback(callback)
.build()
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
CoroutineScope(Dispatchers.IO).launch {
val data = computePrepopulateData(assets_file_name)
data.forEach { user ->
dao.get().insert(user)
}
}
}
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("SELECT * FROM $table_name")
suspend fun getAllUser(): List
CoroutineScope(Dispatchers.IO).launch {
repository.getData().let {
listUser.postValue(it)
}
}
app:list="@{viewModel.listUser}"
ANSWER
Answered 2022-Feb-26 at 16:08Your DAO returns suspend fun getAllUser(): List
, meaning it's a one time thing. So when the app starts the first time, the DB initialization is not complete, and you get an empty list because the DB is empty. Running the app the second time, the initialization is complete so you get the data.
How to fix it:
- Switch
getAllUser()
to return aFlow
:
// annotations omitted
fun getAllUser(): Flow>
- Switch
insertUser
to use aList
// annotations omitted
suspend fun insertUser(users: List)
The reason for this change is reducing the number of times the Flow
will emit. Every time the DB changes, the Flow
will emit a new list. By inserting a List
instead of inserting a single User
many times the (on the first run) Flow
will emit twice (an empty list + the full list) compared to number of user times with a single insert.
Another way to solve this issue is to use a transaction + insert a single user.
- I recommend you use viewModelScope inside the
ViewModel
to launch coroutines so it's properly canceled when theViewModel
is destroyed.
QUESTION
I'm using the MVI pattern in a project so I wrote both State and Event classes. For the Event part inside the viewModel I'm using a private property _event of type MutableLiveData, and exposing it to the Activity as a LiveData, this way:
private val _event = MutableLiveData()
val event: LiveData
get() {
return _event
}
Everything worked well until I needed to use the SingleLiveEvent class (instead of its supertype MutableLiveData) found in this sample code from Google; the SingleLiveEvent class extends MutableLiveData which in turn extends LiveData.
So I thought that I could write something like this:
private val _event: LiveData = SingleLiveEvent()
val event: LiveData
get() {
return _event
}
But i get this error in the editor:
Type mismatch. Required: LiveData Found: SingleLiveEvent
What am I missing? SingleLiveEvent is a subtype of LiveData and it has the same type parameter, so why won't this assignment work?
Thanks in advance
ANSWER
Answered 2021-Jun-24 at 13:42Not 100% sure, but I would bet you auto-converted the SingleLiveEvent
Java code to Kotlin, and it declared it as a subclass of MutableLiveData
instead of MutableLiveData
.
QUESTION
I followed a lot of tutorials/ articles and the googles architecture sample
No matter what I try I keep getting an error saying that I need to attach Fragment to @AndroidEntryPoint annotated Activity. I've setup everything correctly but still can't get this to work properly.
java.lang.IllegalStateException: Hilt Fragments must be attached to an @AndroidEntryPoint Activity. Found: class com.nikolam.colorme.HiltTestActivity
at dagger.hilt.internal.Preconditions.checkState(Preconditions.java:83)
at dagger.hilt.android.internal.managers.FragmentComponentManager.createComponent(FragmentComponentManager.java:75)
at dagger.hilt.android.internal.managers.FragmentComponentManager.generatedComponent(FragmentComponentManager.java:64)
at com.nikolam.main_feature.presenter.main_screen.Hilt_MainFragment.generatedComponent(Hilt_MainFragment.java:80)
at com.nikolam.main_feature.presenter.main_screen.Hilt_MainFragment.inject(Hilt_MainFragment.java:102)
at com.nikolam.main_feature.presenter.main_screen.Hilt_MainFragment.initializeComponentContext(Hilt_MainFragment.java:63)
at com.nikolam.main_feature.presenter.main_screen.Hilt_MainFragment.onAttach(Hilt_MainFragment.java:55)
at androidx.fragment.app.Fragment.onAttach(Fragment.java:1783)
at com.nikolam.main_feature.presenter.main_screen.Hilt_MainFragment.onAttach(Hilt_MainFragment.java:45)
at androidx.fragment.app.Fragment.performAttach(Fragment.java:2911)
at androidx.fragment.app.FragmentStateManager.attach(FragmentStateManager.java:464)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:275)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1971)
at androidx.fragment.app.BackStackRecord.commitNow(BackStackRecord.java:305)
at com.nikolam.colorme.main_feature.MainFragmentTest$whenMainActivityLaunchedNavigatorIsInvokedForFragment$$inlined$launchFragmentInHiltContainer$1.perform(HiltExt.kt:46)
at androidx.test.core.app.ActivityScenario.lambda$onActivity$2$ActivityScenario(ActivityScenario.java:660)
at androidx.test.core.app.ActivityScenario$$Lambda$4.run(Unknown Source)
at androidx.test.core.app.ActivityScenario.onActivity(ActivityScenario.java:670)
at com.nikolam.colorme.main_feature.MainFragmentTest.whenMainActivityLaunchedNavigatorIsInvokedForFragment(MainFragmentTest.kt:85)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at dagger.hilt.android.internal.testing.MarkThatRulesRanRule$1.evaluate(MarkThatRulesRanRule.java:106)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:575)
at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$0(SandboxTestRunner.java:263)
at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:89)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Now here is my setup.
Dependencies
testImplementation(TestLibraryDependency.HILT_ANDROID_TESTING)
kaptTest(TestLibraryDependency.HILT_ANDROID_TESTING_COMPILER)
testImplementation(TestLibraryDependency.TEST_RUNNER)
testImplementation(TestLibraryDependency.ESPRESSO_CORE)
testImplementation(TestLibraryDependency.ANDROIDX_TEST_RULES)
testImplementation(TestLibraryDependency.ANDROIDX_CORE_TESTING)
testImplementation(TestLibraryDependency.ANDROIDX_TEST_EXT)
This is my test
@HiltAndroidTest
@Config(application = HiltTestApplication::class, maxSdk = Build.VERSION_CODES.P)
@RunWith(RobolectricTestRunner::class)
class MainFragmentTest {
@get:Rule()
var hiltAndroidRule = HiltAndroidRule(this)
@Before
fun init() {
hiltAndroidRule.inject()
}
@Test
fun whenMainActivityLaunchedNavigatorIsInvokedForFragment() {
// launchActivity()
// GIVEN - On the home screen
val navController = mock(NavController::class.java)
var fragment = launchFragmentInHiltContainer() {
Navigation.setViewNavController(this.view!!, navController)
}
// WHEN - Click on the "+" button
onView(withId(R.id.add_floating_action)).perform(ViewActions.click())
// THEN - Verify that we navigate to the add screen
verify(navController).navigate(
Uri.parse(UPLOAD_DEEPLINK)
)
}
inline fun launchFragmentInHiltContainer(
fragmentArgs: Bundle? = null,
@StyleRes themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
crossinline action: Fragment.() -> Unit = {}
) {
val startActivityIntent = Intent.makeMainActivity(
ComponentName(
ApplicationProvider.getApplicationContext(),
HiltTestActivity::class.java
)
).putExtra(EmptyFragmentActivity.THEME_EXTRAS_BUNDLE_KEY, themeResId)
ActivityScenario.launch(startActivityIntent).onActivity { activity ->
val fragment: Fragment = activity.supportFragmentManager.fragmentFactory.instantiate(
Preconditions.checkNotNull(T::class.java.classLoader),
T::class.java.name
)
fragment.arguments = fragmentArgs
activity.supportFragmentManager
.beginTransaction()
.add(android.R.id.content, fragment, "")
.commitNow()
fragment.action()
}
}
(I also have their custom runner)
I also have a debug source set that contains HiltTestActivity like here, alongside debug manifest.
Besides this, I'd like to somehow inject and initialize my NavigationManager (wrapper class around navController) in my Activity. Is there a way to achieve this? When I tried to use my MainActivity for tests I kept getting errors because my lateinit @Injects weren't getting initialized...
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding;
private lateinit var navController : NavController
@Inject
lateinit var navManager: NavManager
private fun initNavManager() {
navManager.setOnNavEvent {
navController.navigate(it)
}
ANSWER
Answered 2021-Mar-24 at 08:05In my case I had to use
hilt {
enableTransformForLocalTests = true
}
Since the Roboelectric/ Hilt has 20 bugs currently being tracked it was hard to pinpoint what workaround was the case :)
QUESTION
Im trying to make mvvm pattern with repository and servicelocator to use mock's or remote calls, it depends on flavour. What happening now,is that my liveData is not updating after i receive response from server. So for now i always have a empty list.
I use this google sample to trying make it. sample
My code below, using remote serviceLocator Appreciate your help.
class TestActivity : AppCompatActivity(){
private val viewModel = TestViewModel(ServiceLocator.provideTasksRepository())
private lateinit var binding : TestBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.test)
binding.viewmodel = viewModel
setupRecyclerView()
}
private fun setupRecyclerView() {
binding.viewmodel?.run {
binding.recyclerViewTest.adapter = TestAdapter(this)
}
}
}
class TestAdapter(private val viewModel : TestViewModel) : ListAdapter(TestDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = TestViewHolder.from(parent)
override fun onBindViewHolder(holder: TestViewHolder, position: Int) {
val item = getItem(position)
holder.bind(viewModel, item)
}
class TestViewHolder private constructor(val binding: ItemTestBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(viewModel: TestViewModel, item: ResponseEntity) {
binding.viewmodel = viewModel
binding.game = item
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): TestViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemTestBinding.inflate(layoutInflater, parent, false)
return TestViewHolder(binding)
}
}
}
}
class MyRepository(private val testRemoteDataSource: IDataSource) :
ITestRepository {
override suspend fun getList() = testRemoteDataSource.getList()
}
class TestViewModel(private val testRepository: MyRepository) : ViewModel() {
private var _items = MutableLiveData>()
val items: LiveData> = _items
init {
refreshList()
}
private fun refreshList() {
viewModelScope.launch {
_items = testRepository.getList()
}
}
}
object ServiceLocator {
var testRepository: MyRepository? = null
fun provideTasksRepository(): MyRepository {
synchronized(this) {
return testRepository ?: createTestRepository()
}
}
private fun createTestRepository(): MyRepository {
val newRepo = MyRepository(
MyRemoteDataSource(RetrofitClient.apiInterface)
)
testRepository = newRepo
return newRepo
}
}
class MyRemoteDataSource(private val retroService: IService) :
IDataSource {
private var responseEntityLiveData: MutableLiveData> =
MutableLiveData>()
override suspend fun getGames(): MutableLiveData> {
retroService.getList()
.enqueue(object : Callback> {
override fun onFailure(
call: Call>,
t: Throwable
) {
responseEntityLiveData.value = emptyList()
}
override fun onResponse(
call: Call>,
response: Response>
) {
responseEntityLiveData.value = response.body()
}
})
return responseEntityLiveData
}
}
ANSWER
Answered 2020-Dec-20 at 15:46My guess is that mixing Coroutines suspending function with Retrofit and LiveData leads to some side effect here.
I do not have a single solution, but some points they can help you.
In general I would avoid mixing LiveData with suspending functions. LiveData is concept of caching data for the UI/ViewModel layer. Lower layers do not need to know anything like Android concrete stuff like LiveData. More information here
In your repository or dataSource can either use a suspending function that returns a single value or Coroutines Flow that can emit more than one value. In your ViewModel you can then map those results to your LiveData.
DataSource
In your DataSource you can use suspendCoroutine or suspendCancellableCoroutine
to connect Retrofit (or any other callback interface) with Coroutines:
class DataSource(privat val retrofitService: RetrofitService) {
/**
* Consider [Value] as a type placeholder for this example
*/
fun suspend fun getValue(): Result = suspendCoroutine { continuation ->
retrofitService.getValue().enqueue(object : Callback {
override fun onFailure(call: Call>,
throwable: Throwable) {
continuation.resume(Result.Failure(throwable)))
}
override fun onResponse(call: Call>,
response: Response>) {
continuation.resume(Result.Success(response.body()))
}
}
}
}
Result Wrapper
You can wrap the response to your own Result
type like:
sealed class Result {
/**
* Indicates a success state.
*/
data class Success(val data: T) : Result()
/**
* Indicates an error state.
*/
data class Failure(val throwable: Throwable): Result
}
I leave the Repository out from this example and call directly the DataSource.
ViewModel
Now in your ViewModel
you can launch the coroutine, getting the Result
and map it to the LiveData.
class TestViewModel(private val dataSource: DataSource) : ViewModel() {
private val _value = MutableLiveData>()
val value: LiveData = _value
private val _error = MutableLiveData()
val error: LiveData = _error
init {
getValue()
}
private fun getValue() {
viewModelScope.launch {
val result: Result = dataSource.getValue()
// check wether the result is of type Success or Failure
when(result) {
is Result.Success -> _value.postValue(result.data)
is Result.Failure -> _error.value = throwable.message
}
}
}
}
I hope that helps you a bit.
QUESTION
The following code is from the project.
1: In my mind,a suspend fun should be launched in another suspend fun or viewModelScope.launch{ }
, withContext{ }
... , filterItems()
is only a normal function, I don't know why filterItems()
need to be wrapped with viewModelScope.launch{ }
in the function filterTasks()
, could you tell me ?
2: In the function filterTasks()
, viewModelScope.launch{ }
will launch in coroutines, it's asynchronous, I think return result
maybe be launched before I get the result
from viewModelScope.launch{}
, so the result
maybe null, is the code correct?
Code
private fun filterTasks(tasksResult: Result>): LiveData> {
val result = MutableLiveData>()
if (tasksResult is Success) {
isDataLoadingError.value = false
viewModelScope.launch {
result.value = filterItems(tasksResult.data, getSavedFilterType())
//return filterItems(tasksResult.data, getSavedFilterType()) //It will cause error.
}
} else {
result.value = emptyList()
showSnackbarMessage(R.string.loading_tasks_error)
isDataLoadingError.value = true
}
return result //I think it maybe be launched before I get the result from viewModelScope.launch{}
}
private fun filterItems(tasks: List, filteringType: TasksFilterType): List {
val tasksToShow = ArrayList()
// We filter the tasks based on the requestType
for (task in tasks) {
when (filteringType) {
ALL_TASKS -> tasksToShow.add(task)
ACTIVE_TASKS -> if (task.isActive) {
tasksToShow.add(task)
}
COMPLETED_TASKS -> if (task.isCompleted) {
tasksToShow.add(task)
}
}
}
return tasksToShow
}
ANSWER
Answered 2020-Sep-16 at 22:26yes, you are right. but if you looked up the implementation of the launch {} such in lifecycleScope.launch {} or viewModelScope.launch {} you would find out the "block" which is "the coroutine code which will be invoked in the context of the provided scope" is cast to be suspend, so any block of code between launch {} is suspend code block. so in your example filterItems is cast to suspend under the hood and it's wrapped with viewModelScope.launch{ } to do its heavy task not in main thread.
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
// the below line is doing the magic
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
QUESTION
The following code is from the project.
The function of tasksRepository.refreshTasks()
is to insert data from remote server to local DB, it's a time consuming operation.
In class TasksViewModel
, asksRepository.refreshTasks()
is wrapped with viewModelScope.launch{}
, it means launch and careless.
1: How can I guarantee tasksRepository.observeTasks().distinctUntilChanged().switchMap { filterTasks(it) }
to return the latest result?
2: I don't know how distinctUntilChanged()
work, will it keep listening to return the latest result in whole Lifecycle ?
3: What's happened if I use tasksRepository.observeTasks().switchMap { filterTasks(it) }
instead of tasksRepository.observeTasks().distinctUntilChanged().switchMap { filterTasks(it) }
Code
class TasksViewModel(..) : ViewModel() {
private val _items: LiveData> = _forceUpdate.switchMap { forceUpdate ->
if (forceUpdate) {
_dataLoading.value = true
viewModelScope.launch {
tasksRepository.refreshTasks()
_dataLoading.value = false
}
}
tasksRepository.observeTasks().distinctUntilChanged().switchMap { filterTasks(it) }
}
...
}
class DefaultTasksRepository(...) : TasksRepository {
override suspend fun refreshTask(taskId: String) {
updateTaskFromRemoteDataSource(taskId)
}
private suspend fun updateTasksFromRemoteDataSource() {
val remoteTasks = tasksRemoteDataSource.getTasks()
if (remoteTasks is Success) {
tasksLocalDataSource.deleteAllTasks()
remoteTasks.data.forEach { task ->
tasksLocalDataSource.saveTask(task)
}
} else if (remoteTasks is Result.Error) {
throw remoteTasks.exception
}
}
override fun observeTasks(): LiveData>> {
return tasksLocalDataSource.observeTasks()
}
}
ANSWER
Answered 2020-Sep-11 at 02:57switchMap
- The returned LiveData delegates to the most recent LiveData created by calling switchMapFunction with the most recent value set to source, without changing the reference. DocYes, it'll keep listening to return the latest result in whole Lifecycle.
distinctUntilChanged
creates a new LiveData object that does not emit a value until the source LiveData value has been changed. The value is considered changed ifequals()
yieldsfalse
.Yes you can use that too but it'll keep emitting the values even the values are the same as the last emitted value. e.g. first emitted value is ["aman","bansal"] and the second is the same ["aman","bansal"] which you don't want to emit since the values are same. So you use
distinctUntilChanged
to make sure it won't emit the same value until changed.
I hope this helped.
QUESTION
I have a callback method in my fragment which gets called from it's ViewModel. It initializes the variable in the OnCreateView()
method of the fragment, but when the ViewModel calls it to use it, its null.
I am thinking that it has something to do with maybe the VM getting recreated somehow? I just can't seem to figure it out.
I am following this answer's of how the VM drives the UI. They provide Google's sample of a callback interface being created (TasksNavigator.java), Overriding the method in the View (TasksActivity.java), and then calling that method from the VM (TasksViewModel.java) but it doesn't seem to work for me.
Fragment
class SearchMovieFragment : Fragment(), SearchNavigator {
companion object {
fun newInstance() = SearchMovieFragment()
}
private lateinit var searchMovieFragmentViewModel: SearchMovieFragmentViewModel
private lateinit var binding: SearchMovieFragmentBinding
private lateinit var movieRecyclerView: RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
searchMovieFragmentViewModel = ViewModelProvider(this).get(SearchMovieFragmentViewModel::class.java)
binding = DataBindingUtil.inflate(inflater, R.layout.search_movie_fragment, container, false)
binding.viewmodel = searchMovieFragmentViewModel
searchMovieFragmentViewModel.setNavigator(this)
setUpRecyclerView(container!!.context)
return binding.root
}
private fun setUpRecyclerView(context: Context) {
movieRecyclerView = binding.searchMovieFragmentRecyclerView.apply {
this.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
}
val adapter = MovieListAdapter()
binding.searchMovieFragmentRecyclerView.adapter = adapter
searchMovieFragmentViewModel.movieList.observe(viewLifecycleOwner, Observer {
adapter.submitList(it)
})
}
override fun openDetails() {
Log.d("TEST", "opening details")
}
}
ViewModel
class SearchMovieFragmentViewModel : ViewModel(), MovieSearchItemViewModel {
private lateinit var searchNavigator: SearchNavigator
val editTextContent = MutableLiveData()
var movieList = Repository.getMovieList("batman")
fun setNavigator(_searchNavigator: SearchNavigator) {
this.searchNavigator = _searchNavigator
if (searchNavigator != null) {
Log.d("TEST", "its not null $searchNavigator") // Here it is not null
}
}
private fun getMovieDetail(movieId: String) {
val movie = Repository.getMovieDetail(movieId)
Log.d("TEST", "checking ${this.searchNavigator}") // Here is where I call it but it is null
// searchNavigator.openDetails()
}
private fun getMovieList(movieSearch: String): MutableLiveData> = Repository.getMovieList(movieSearch)
override fun displayMovieDetailsButton(movieId: String) {
Log.d("TEST", "button clicked $movieId")
getMovieDetail(movieId)
}
}
CallBack Interface
interface SearchNavigator {
fun openDetails()
}
ANSWER
Answered 2020-Sep-08 at 07:04Initiate ViewModel in below method of fragment
override onActivityCreated(@Nullable final Bundle savedInstanceState){
searchMovieFragmentViewModel = ViewModelProvider(this).get(SearchMovieFragmentViewModel::class.java)
}
I will recommend use live data to create connection between ViewModel and and Fragment it will be safer and correct approach.
Trigger openDetails based on the trigger's from your live data.It's forbidden to send your view(context
) instance to ViewModel even if you wrap it as there is high probability of memory leaks.
But if you still want to follow this approach then you should Register and unregister fragment instance in your ViewModel (keep a list of SearchNavigator) it onStop()
and onStart()
.
and loop through them to call openDetails
QUESTION
val task: LiveData = _task
val completed: LiveData = _task.map { input: Task? ->
input?.isCompleted ?: false
}
Code B
val task: LiveData = _task
val completed = Transformations.map(_task){input: Task? ->
input?.isCompleted ?: false
}
ANSWER
Answered 2020-Aug-25 at 07:33Yes, it is absolutely identical because LiveData.map
is an extension function that provided from Transformations.kt
file that is a part of dependency:
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
This is what map
extension function does which is absolutely identical to Code B:
inline fun LiveData.map(crossinline transform: (X) -> Y): LiveData =
Transformations.map(this) { transform(it) }
QUESTION
I've got an android app setup for dependency injection using Hilt, and would like to unit test my fragments.
I'm currently creating my view model using:
private val viewModel: ExampleViewModel by viewModels()
And I am creating the fragment for testing using the code from here
I need to replace this ExampleViewModel with a mock, how would I go about doing this?
ANSWER
Answered 2020-Aug-12 at 20:39I will paste here the "danysantiago" response in a issue (https://github.com/google/dagger/issues/1972) related to your question:
Hilt ViewModel extension works by declaring modules that bind assisted factories to a map and not by binding concrete ViewModels. Therefore, what you want to do is bind the assisted factory of the concrete ViewModel using the key of the abstract ViewModel so that when HiltViewModelFactory looks up the factory based on class key it uses the assisted factory for the concrete ViewModel. This is suuuper obscure and hence why I mean not 'easily' available.
However, if you can expand on the test case your are trying to write that could help us provide some guidance, I'm not sure if you are trying to mock/fake the ViewModel itself for tests, but Hilt testing APIs should allow you to replace dependencies in the ViewModel so you can write a test with the Fragment and the ViewModel.
Community Discussions, Code Snippets contain sources that include Stack Exchange Network
Vulnerabilities
No vulnerabilities reported
Install architecture-samples
Support
Find, review, and download reusable Libraries, Code Snippets, Cloud APIs from over 650 million Knowledge Items
Find more librariesExplore Kits - Develop, implement, customize Projects, Custom Functions and Applications with kandi kits
Save this library and start creating your kit
Share this Page