2

I am using Navigation Compose, and I am trying to pass an entire object to the next screen, So I am using a shared ViewModel through hilt, and created a mutablestate variable of that object and want to get its value in the next screen. Like This

var campaign = mutableStateOf<Campaign?>(null)
private set

fun addCampaign(campaign: Campaign){
  this.campaign.value = campaign
}

where Campaign is just a data-class.

In my Screen in Navigation

 LazyColumn (
        content =
        {
            items(viewModel.campaignListCurrent){ campaign ->
                CampaignItem(
                    image = campaign.brand?.image ?: "",
                    title = campaign.name ,
                    id = campaign.id ,
                    description =campaign.description ,
                    date =campaign.createdAt ) {

                    viewModel.addCampaign(
                        CampaignsViewModel.Campaign(...) // the "campaign" object is used to fill this   
                    )
                    Timber.tag("CampaignObject").v(viewModel.campaign.value.toString())
                    viewModel.changed = true
                    navController.navigate(Screen.CampaignDetailsScreen.route)
                }
            }
        })

I can see when I log the data that it was stored successfully. yet in the next screen when I get the data from the same ViewModel, it's null. even though its a mutablestate and supposed to change its value and be observables, I don't think I get mutablestate behavior at all, and any link for a proper explanation for it will be appreciated.

  val campaign by remember { viewModel.campaign }

    Timber.v("CampaignDetailsScreen2: " + campaign.toString())

can someone explain to me why it doesn't work here, even if I used Launched effect?? why doesn't change its value here?

Richard Onslow Roper
  • 5,477
  • 2
  • 11
  • 42
Ahmed Rabee
  • 185
  • 14
  • are viewModels the same? – Sergei Mikhailovskii Sep 04 '22 at 12:38
  • yes, its the same ViewModel in both of them and I inject it using Hilt – Ahmed Rabee Sep 04 '22 at 13:07
  • just in case, can u please check that their hashcodes are the same – Sergei Mikhailovskii Sep 04 '22 at 13:19
  • It's actually different after I log it, CampaignsViewModel@3891dec and @644c420, is it because I am using hiltViewModel() here? – Ahmed Rabee Sep 04 '22 at 13:56
  • Use `CompositionLocalProvider` with `LocalViewModelStoreOwner` like [this](https://stackoverflow.com/a/68971296/9636037) to use shared viewmodel – Abhimanyu Sep 04 '22 at 15:25
  • Is your module a singleton? If not it might be that Hilt creating a new viewModel for each screen is the problem, can't think of other reasons. – Arthur Kasparian Sep 04 '22 at 18:08
  • I am sure that the issue is Hilt creating a new ViewModel each time, as I am using hiltViewModel() function to inject the ViewModel as a constructor to the composable function, I won't be able to CompositionLocalProvider as it seems a valid solution but I am using these compostable functions inside TabPage thus I didn't create navigation for them in my navGraph, The fact that Navigation in compose doesn't allow to pass a parceable object is a major issue, how do I create a singleton from HiltViewmodel() as this would solve it for sure. – Ahmed Rabee Sep 05 '22 at 14:16

2 Answers2

2

I managed to solve this by Creating a SharedViewModel instance in the NavGraph to make sure it is the same Instance used in both composable functions.

    @Composable
fun CampaignsScreen(
    navController : NavController,
    viewModel: CampaignsViewModel
) {

....

    @Composable
fun CampaignDetailsScreen(
    navController : NavController,
    viewModel: CampaignsViewModel
) {

and in my NavGraph I passed the ViewModel as HiltViewmodel in the constructor and from it to the two screens

    @Composable
    fun SetUpNavGraph (navController : NavHostController,
                       campaignViewModel: CampaignsViewModel = hiltViewModel()) {
...........
    
                loginNavGraph(navController = navController)
        
                homeNavGraph(navController = navController, campaignsViewModel = campaignViewModel)
            }
        }

and in the home NavGraph I passed the parametere and its the same Instance in both

fun NavGraphBuilder.homeNavGraph(
    navController : NavHostController,
    campaignsViewModel: CampaignsViewModel
){

    navigation(startDestination = Screen.HomeScreen.route, route = HOME_GRAPH_ROUTE) {
      ................
        composable(Screen.Campaigns.route){
            CampaignsScreen(navController = navController, viewModel = campaignsViewModel)
        }
        composable(Screen.CampaignDetailsScreen.route){
             CampaignDetailsScreen(navController, viewModel = campaignsViewModel)
        }

    }
}

You can use backstackEntry and PreviouseBackStack to solve this but it won't work if you have a JSON Object within your object as you will need to Serialize and Deserialize this object manually and it will take more time and code to achieve the same goal for a shared ViewModel to pass Parclized object. until Google Solve passing Parclized objects in Compose Navigation I recommend backStack Entry if you don't have a JSON object within your paralyzed object and if you do, use a shared ViewModel between the tow composable function.

Ahmed Rabee
  • 185
  • 14
-1

Why would it work if you are just remembering a local value and not even reading from the proper model?

val campaign = viewModel.campaign

Can't just use remember willy-nilly without understanding the proper usage.

Richard Onslow Roper
  • 5,477
  • 2
  • 11
  • 42
  • The issue is not using remember here, its that the new composable function creates a new instance from the same ViewModel, so it doesn't store the data within it. – Ahmed Rabee Sep 05 '22 at 14:53
  • Well then you have your answer don't you. Close the question, answer your own post and you'll be able to accept it within 2 days of posting. Case closed. – Richard Onslow Roper Sep 05 '22 at 15:28
  • I still don't have the answer, I cant create a singletone using hiltViewModel() nor I can pass a parceable object through Navigation? I still need a way to get one instance of the same viewmodel in both composable function, once I figure it out I will post the answer here – Ahmed Rabee Sep 05 '22 at 15:34