I'd say your instincts were correct to move the chart instance into the view model, but, as you noted, context dependencies can become a hassle when they are required for objects other than views. To me, this becomes a question of dependency injection where the dependency is the context or, in a broader sense, the entire data chart. I'd be interested in knowing how you source your view model, but I'll assume it relies on an Android view model provider (via by viewModels() or some sort of ViewModelProvider.Factory).
An immediate solution to this issue is to convert the view model into a subclass of an AndroidViewModel which provides reference to the application context via the view model's constructor. While it remains an anti-pattern and should used sparingly, the Android team has recognized certain use cases to be valid. I personally do not use the AndroidViewModel because I believe it to be a crude solution to a problem which could otherwise be solved with refinements to the dependency graph. However, it's sanctioned by the official documentation, and this is only my personal opinion. From experience, I must say its use makes testing a view model quite a nightmare after-the-fact. If you are interested in a dependency injection library, I'd highly recommend the new Hilt implementation which recently launched a stable 1.0.0 release just this past month.
With this aside, I'll now provide two possible solutions to your predicament: one which utilizes the AndroidViewModel and another which does not. If your view model already has other dependencies outside of the context, the AndroidViewModel solution won't save you much overhead as you'd likely already be instantiating a ViewModelProvider.Factory at some point. These solutions will be considering the scope of an Android Fragment but could easily be implemented in an Activity or DialogFragment as well with some tweaks to lifecycle hooks and whatnot.
With AndroidViewModel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
class MyViewModel(application: Application) : AndroidViewModel(application) {
val dataChart: DataChart
init {
dataChart = DataChart(application.applicationContext)
}
}
where the fragment could be
class MyFragment : Fragment() {
private val viewModel: MyViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View { ... }
}
Without AndroidViewModel
import androidx.lifecycle.ViewModel
class MyViewModel(args: Args) : ViewModel() {
data class Args(
val dataChart: DataChart
)
val dataChart: DataChart = args.dataChart
}
where the fragment could be
class MyFragment : Fragment() {
private lateinit var viewModel: MyViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val applicationContext: Context = requireContext().applicationContext
val dataChart = DataChart(applicationContext)
val viewModel: MyViewModel by viewModels {
ArgsViewModelFactory(
args = MyViewModel.Args(
dataChart = dataChart,
),
argsClass = MyViewModel.Args::class.java,
)
}
this.viewModel = viewModel
...
}
}
and where ArgsViewModelFactory is a creation of my own as shown below
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class ArgsViewModelFactory<T>(
private val args: T,
private val argsClass: Class<T>,
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = modelClass.getConstructor(
argsClass,
).newInstance(
args,
)
}
edit (via Hilt module):
@Module
@InstallIn(...)
object DataChartModule {
@Provides
fun provideDataChart(
@ApplicationContext context: Context,
): DataChart = DataChart(context)
}