I have a problem after updating to the new MvvmCross 5.2.
I have forced uninstalled MvvmCross.Droid.Shared and after update all packages. I then got some errors with MvxFragment, so I replaced it with MvxFragmentPresentation. Additionally, I replaced MvxCachingFragmentCompatActivity with MvxAppCompatActivity and I'm now using the new MvxAppCompatViewPresenter. All works well, and the app running good. Except after I select logout in menu I'm taken to the LoginViewModel and when I want Login again I get this error
Fragment already active.
Can someone help me?
My test project is HERE on github.
It fail here, by the ShowViewModel
public class MainViewModel : BaseViewModel
{
public void ShowMenu()
{
ShowViewModel<MenuViewModel>();
}
}
The issue is that you are mixing your methods for presenting in MvvmCross. With MvvmCross 5.x a new prefer way to navigate was introduced using the IMvxNavigationService. For new apps it is suggested that you rather make use of the IMvxNavigationService over the prior ShowViewModel. It is advised that you do not mix the use of the two different ways to navigate as you may get some strange behaviour.
Switching to that IMvxNavigationService which you are already using on the LoginViewModel will solve the exception you are getting.
protected readonly IMvxNavigationService _mvxNavigationService;
public MainViewModel(IMvxNavigationService mvxNavigationService)
{
_mvxNavigationService = mvxNavigationService;
}
public void ShowMenu()
{
_mvxNavigationService.Navigate<MenuViewModel>();
}
Additionally, you will want to remove adding HomeFragment to the backstack to prevent seeing a white page when navigation back.
[MvxFragmentPresentation(typeof(MainViewModel), Resource.Id.content_frame)]
public class HomeFragment : BaseFragment<HomeViewModel>
See pull request for full details of changes.
Additional notes
Rather than explicitly specifying MvxAppCompatViewPresenter in your Setup which inherits MvxAndroidSetup you can rather inherit from MvxAppCompatSetup which will automatically make use of the MvxAppCompatViewPresenter as well as register additional AndroidViewAssemblies relating to support libraries (see link to which assemblies) and FillTargetFactories for the MvxAppCompatSetupHelper.
Related
I'm a newbie in Android.
I am trying to figure out how to create a stateful fragment based on MVVM with a SavedStateHandle injection.
I have created an empty project in Android Studio based on a template with bottom navigation. Then I added a text input into DashboardFragment. I want the text of this input to be saved when user switches between tabs.
At the moment I get access to VM as follows:
private val application = activity?.application
private val viewModel: DashboardViewModel by viewModels {
SavedStateViewModelFactory(application, activity as SavedStateRegistryOwner)
}
And I get an exception
ava.lang.IllegalArgumentException: SavedStateProvider with the given key is already registered
Please, help me to find out what is wrong
Source code: https://github.com/ideogram-software/statefulfragments-tests/
The problem was in androidx.navigation:navigation-fragment-ktx and androidx.navigation:navigation-ui-ktx dependencies version. They were 2.3.5.
I have changed them as follows (thanks to #ianhanniballake)
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.0-rc01'
implementation 'androidx.navigation:navigation-ui-ktx:2.4.0-rc01'
and the problem has gone
I was using the beta version of the navigation component and my code was working fine, I then swapped back to the latest non beta version and it stopped working, but from what I can see I wasn't using anything beta-ish :-)
I want to scope view models to navigation graphs I do this by getting the nav controller store owner, this works for ViewModel but not for AndroidViewModel, when trying it throws an error because it expects a no zero argument constructor, which isn't the case for AndroidViewModel but as mentioned above this works in the beta version,
private ViewModelStoreOwner getStoreOwner() {
NavController navController = Navigation
.findNavController(requireActivity(), R.id.root_navigator_fragment);
return navController.getViewModelStoreOwner(R.id.root_navigator);
}
Use it with
singleCardViewModel = new ViewModelProvider(getStoreOwner()).get(SingleCardViewModel.class);
so a few ways I can make this work is
1) swap the android view model for a normal view model, this can be done for most however some view models require application for my database calls,
2) I could swap them for view models and then make factories to pass it the application,
3) I can replace the getStoreOwner for the fragment by just passing 'this' instead of getStoreOwner but that probably means more testing to make sure its not holding values
4) Continue using the beta version, I'd rather avoid this if possible
none of these are really ideal, shouldn't this just work? am i doing something obviously wrong?
I'm trying to learn the Arrow library and improve my functional programming by transitioning some of my Android Kotlin code from more imperative style to functional style. I've been doing a type of MVI programming in the application to make testing simpler.
"Traditional" Method
ViewModel
My view model has a LiveData of the view's state plus a public method to pass user interactions from the view to the viewmodel so the view model can update state in whatever way is appropriate.
class MyViewModel: ViewModel() {
val state = MutableLiveData(MyViewState()) // MyViewState is a data class with relevant data
fun instruct(intent: MyIntent) { // MyIntent is a sealed class of data classes representing user interactions
return when(intent) {
is FirstIntent -> return viewModelScope.launch(Dispatchers.IO) {
val result = myRoomRepository.suspendFunctionManipulatingDatabase(intent.myVal)
updateStateWithResult(result)
}.run { Unit }
is SecondIntent -> return updateStateWithResult(intent.myVal)
}
}
}
Activity
The Activity subscribes to the LiveData and, on changes to state, it runs a render function using the state. The activity also passes user interactions to the view model as intents (not to be confused with Android's Intent class).
class MyActivity: AppCompatActivity() {
private val viewModel = MyViewModel()
override fun onCreateView() {
viewModel.state.observe(this, Observer { render(it) })
myWidget.onClickObserver = {
viewModel.instruct(someIntent)
}
}
private fun render(state: MyViewState) { /* update view with state */ }
}
Arrow.IO Functional Programming
I'm having trouble finding examples that aren't way over my head using Arrow's IO monad to make impure functions with side effects obvious and unit-testable.
View Model
So far I have turned my view model into:
class MyViewModel: ViewModel() {
// ...
fun instruct(intent: MyIntent): IO<Unit> {
return when(intent) {
is FirstIntent -> IO.fx {
val (result) = effect { myRoomRepository.suspendFunctionManipulatingDatabase(intent.myVal) }
updateStateWithResult(result)
}
is SecondIntent -> IO { updateStateWithResult(intent.myVal) }
}
}
}
I do not know how I am supposed to make this IO stuff run in Dispatcher.IO like I've been doing with viewModelScope.launch. I can't find an example for how to do this with Arrow. The ones that make API calls all seem to be something other than Android apps, so there is no guidance about Android UI vs IO threads.
View model unit test
Now, because one benefit I'm seeing to this is that when I write my view model's unit tests, I can have a test. If I mock the repository in order to check whether suspendFunctionManipulatingDatabase is called with the expected parameter.
#Test
fun myTest() {
val result: IO<Unit> = viewModel.instruct(someIntent)
result.unsafeRunSync()
// verify suspendFunctionManipulatingDatabase argument was as expected
}
Activity
I do not know how to incorporate the above into my Activity.
class MyActivity: AppCompatActivity() {
private val viewModel = MyViewModel()
override fun onCreateView() {
viewModel.state.observe(this, Observer { render(it) })
myWidget.onClickObserver = {
viewModel.instruct(someIntent).unsafeRunSync() // Is this how I should do it?
}
}
// ...
}
My understanding is anything in an IO block does not run right away (i.e., it's lazy). You have to call attempt() or unsafeRunSync() to get the contents to be evaluated.
Calling viewModel.instruct from Activity means I need to create some scope and invoke in Dispatchers.IO right? Is this Bad(TM)? I was able to confine coroutines completely to the view model using the "traditional" method.
Where do I incorporate Dispatchers.IO to replicate what I did with viewModelScope.launch(Dispatchers.IO)?
Is this the way you're supposed to structure a unit test when using Arrow's IO?
That's a really good post to read indeed. I'd also recommend digging into this sample app I wrote that is using ArrowFx also.
https://github.com/JorgeCastilloPrz/ArrowAndroidSamples
Note how we build the complete program using fx and returning Kind at all levels in our architecture. That makes the code polymorphic to the type F, so you can run it using different runtime data types for F at will, depending on the environment. In this case we end up running it using IO at the edges. That's the activity in this case, but could also be the application class or a fragment. Think about this as what'd be the entry points to your apps. If we were talking about jvm programs the equivalent would be main(). This is just an example of how to write polymorphic programs, but you could use IO.fx instead and return IO everywhere, if you want to stay simpler.
Note how we use continueOn() in the data source inside the fx block to leave and come back to the main thread. Coroutine context changes are explicit in ArrowFx, so the computation jumps to the passed thread right after the continueOn until you deliberately switch again to a different one. That intentionally makes thread changes explicit.
You could inject those dispatchers to use different ones in tests. Hopefully I can provide examples of this soon in the repo, but you can probably imagine how this would look.
For the syntax on how to write tests note that your program will return Kind (if you go polymorphic) or IO, so you would unsafeRunSync it from tests (vs unsafeRunAsync or unsafeRunAsyncCancellable in production code since Android needs it to be asynchronous). That is because we want our test to be synchronous and also blocking (for the latter we need to inject the proper dispatchers).
Current caveats: The solution proposed in the repo still doesn't care of cancellation, lifecycle or surviving config changes. That's something I'd like to address soon. Using ViewModels with a hybrid style might have a chance. This is Android so I'd not fear hybrid styles if that brings better productivity. Another alternative I've got in mind would maybe be something a bit more functional. ViewModels end up retaining themselves using the retain config state existing APIs under the hood by using the ViewModelStore. That ultimately sounds like a simple cache that is definitely a side effect and could be implemented wrapped into IO. I want to give a thought to this.
I would definitely also recommend reading the complete ArrowFx docs for better understanding: https://arrow-kt.io/docs/fx/ I think it would be helpful.
For more thoughts on approaches using Functional Programming and Arrow to Android you can take a look to my blog https://jorgecastillo.dev/ my plan is to write deep content around this starting 2020, since there's a lot of people interested.
In the other hand, you can find me or any other Arrow team maintainers in the Kotlinlang JetBrains Slack, where we could have more detailed conversations or try to resolve any doubts you can have https://kotlinlang.slack.com/
As a final clarification: Functional Programming is just a paradigm that resolves generic concerns like asynchrony, threading, concurrency, dependency injection, error handling, etc. Those problems can be found on any program, regardless of the platform. Even within an Android app. That is why FP is an option as valid for mobile as any other one, but we are still into explorations to provide the best APIs to fulfill the usual Android needs in a more ergonomic way. We are in the process of exploration in this sense, and 2020 is going to be a very promising year.
Hopefully this helped! Your thoughts seem to be well aligned with how things should work in this approach overall.
I heard about Timber and was reading github README, but it's quietly confusing me.
Behavior is added through Tree instances. You can install an instance
by calling Timber.plant. Installation of Trees should be done as early
as possible. The onCreate of your application is the most logical
choice.
What behavior?
This is a logger with a small, extensible API which provides utility
on top of Android's normal Log class.
What more does it provide on top of Android's Log?
The DebugTree implementation will automatically figure out from which
class it's being called and use that class name as its tag. Since the
tags vary, it works really well when coupled with a log reader like
Pidcat.
What is DebugTree?
There are no Tree implementations installed by default because every
time you log in production, a puppy dies.
Again, what is a tree implementation? What does it do? And how do I stop killing puppies?
Two easy steps:
Install any Tree instances you want in the onCreate of your
application class.
Call Timber's static methods everywhere throughout your app.
Two easy steps for accomplishing what?
None of this has been explained in the Readme. It's pretty much a description for someone who already knows what it is :/
Problem :-
We do not want to print logs in Signed application as we may sometimes log sensible information . Generally to overcome this developers tend to write if condition before writing log
Example:-
if(BuildConfig.DEBUG) {
Log.d(TAG,userName);
}
so every time you want to print a log you need to write a if condition and a TAG which most times will be class name
Timber tackels these two problems
You just need to check condition once in application class and initialize Timber.plant
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
}
}
}
remaining all places we can just write Timber.d("Message") without any tag or if condition .
If you want a different tag then you can use
Timber.tag("Tag").d("message");
Edit :
Also you can plant your own trees in Timber , and do some cool stuff like Log only warnings and error in release , send warnings and error logs to server etc . eg
import timber.log.Timber;
public class ReleaseTree extends Timber.Tree {
#Override
protected void log(int priority, String tag, String message, Throwable t) {
if (priority == ERROR || priority == WARNING){
//Send to server
}
}
}
You can plant different trees for different build flavours .
Check out this article and have a listen to this podcast
I'm kinda new to Android development so my question might be weird or not even possible. I wouldn't know!
Anyway, I'm building multiple apps that will have a lot of shared elements, so I decided to build a library with those components and use it in all of the apps, rather than stupid copying and pasting code.
For example, the library handles the welcome screen and login/signup flow activities, among other things. So here are the problems this approach might cause:
While the behavior is the same across the apps, but the logo that I show at the welcome screen is different. Right now I populate it with an image resource from the library resources (R class) which will be the same for all apps and is obviously not correct.
The login/signup process is based on Firebase, which will require the app to have a key to be able to use them. Right now I also populate it with a dummy string resource from the library resources.
So my question really boils down to 3 parts:
Is there anyway I could pass this info from the app to the library? can I somehow modify the R class of the library? Or can I use the app's R class from the library? I can also call this part of the library as a function passing the parameters I need. But the first solution looks maybe more clean to me?
Whatever the answer to Q1 is. Where would I do this and how? The library has the welcome activity itself which is supposed to be the first activity in the app. How and where do I do this once the app starts and before the first activity starts?
If what I'm doing is wrong or impossible, is there any other way to achieve it?
Is there anyway I could pass this info from the app to the library?
can I somehow modify the R class of the library? Or can I use the
app's R class from the library? I can also call this part of the
library as a function passing the parameters I need. But the first
solution looks maybe more clean to me?
You don't need to modify the R class because you can override the resource file by creating a file with the same name. But it's not a clean solution because you constantly need to ensure your project and library resources name are the same.
Whatever the answer to Q1 is. Where would I do this and how? The
library has the welcome activity itself which is supposed to be the
first activity in the app. How and where do I do this once the app
starts and before the first activity starts?
Instead of overriding the resources name, you're better to modify your library to receive a configuration as a contract to use the library. Here the sample:
First, create the class for holding the configuration:
public class Configuration {
private int welcomeImageDrawableId;
private int logoDrawableId;
// constructor
public Configuration(int welcomeImageDrawableId, int logoDrawableId) {
this.welcomeImageDrawableId = welcomeImageDrawableId;
this.logoDrawableId = logoDrawableId;
}
// setter and getter.
public int getLogoDrawableId() {
return logoDrawableId;
}
}
Second, use the configuration class for the library by creating a Singleton class which will be used internally by the library:
public class MyLibrary {
private static MyLibrary myLibrary;
private Configuration configuration;
private MyLibrary(){}
private MyLibrary(Configuration configuration) {
this.configuration = configuration;
}
public static MyLibrary getInstance() {
if(myLibrary == null) {
throw new RuntimeException("Need call createInstanceWith method first!!");
}
return myLibrary;
}
public static MyLibrary createInstanceWith(Configuration configuration) {
if(myLibrary == null) {
synchronized(MyLibrary.class) {
if (myLibrary == null) {
myLibrary = new MyLibrary(configuration);
}
}
}
return test;
}
public Configuration getConfiguration() {
return configuration;
}
}
Third, use the configuration class in your library via the singleton class. something like this:
// assume imvLogo is an existing ImageView
Configuration configuration = MyLibrary.getInstance().getConfiguration();
imvLogo.setImageResource(configuration.getLogoDrawableId());
Last, register the contract when the library is used with:
Configuration configuration = new Configuration(R.drawable.welcome, R.drawable.logo);
MyLibrary.createInstanceWith(configuration);
Note: all the code isn't tested yet, error is to be expected.
Apart from the solution above, I also found another way to achieve this whole thing without having to initialize libraries and whatnot.
I think the correct way to do this is to use productFlavors in the library. This allows the library to share the one main set of source code, one main set of resources, then an extra set of resource per app/flavors. This is very sufficient for my purposes.
For more info about build variants and flavors:
https://developer.android.com/studio/build/build-variants