Android ViewModel Using A Repository With Interfaces
Learn how to implement Android ViewModel using a Repository with interfaces for clean architecture in your app.

In this post I will demonstrate a basic template to use when implementing Android ViewModel using a Repository with interfaces.

When I first started with the concept of Android clean architecture, I struggled with the simple concept of Repository implementations. I am sharing this as a simple demonstration to help with anyone with similar struggles getting started.

The blueprints to follow Android clean architecture patterns are quite simple if you take a moment to look at full picture. The app build should have logic used to update the UI separated by logic used to build the data.

ViewModels in Android are a way to persist UI changes to the app. This can be configuration changes such as the device rotation. They are also used to provide access to the data logic of the app such as API data retrieval.

Repositories are used in the app to provide a "Single Source Of Truth" for data processing. They are used in conjunction with the ViewModel to process data requests which in turn updates the state held in the ViewModel for UI changes. Some of the most common uses for repositories are accessing database data, accessing APIs as well as accessing local data sources.

Interfaces are predetermined methods that can be attached to the Repository to ensure a consistent set of behaviors for objects. Multiple classes or objects could implement the same interface.

In the link below I have shared another implementation of a Repository for the ViewModel as a singleton object.

ViewModel Using A Repository With Interfaces

Code

Create a ViewModel Along With ViewModelFactory

When you need to pass along a dependency parameter to a ViewModel, you will need to implement a ViewModelFactory.

In this example, I am creating a single ViewModel that exposes a data val for use in the MainActivity of the sample app. I have created a simple function to make a Repository call to alter the data.

class MainViewModel(private val mainRepo : MainRepository) : ViewModel() {

    val _data : MutableStateFlow<String> = MutableStateFlow<String>("")
    val data : StateFlow<String> = _data.asStateFlow()

    fun processData() {
        _data.update {
            val randomFunction = Random.nextInt(2)
            when(randomFunction){
                0 -> {
                    mainRepo.getNewNumber().toString()
                }
                1-> {
                    mainRepo.getNewName()
                }

                else -> { randomFunction.toString() }
            }

        }
    }
}

class ViewModelFactory(private val mainRepo : MainRepository) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return if(modelClass.isAssignableFrom(com.itgeek25.samplerepositoryinterface.MainViewModel::class.java)){
            com.itgeek25.samplerepositoryinterface.MainViewModel(mainRepo) as T
        } else {
            throw IllegalArgumentException("ViewModel Not Found!!")
        }
    }
}

Create Your Repository and Interface

Next, create your Repository implementing your interface. In this example, the functions in the interface are chosen randomly to update the data val in the ViewModel.

class MainRepository : MyRepoInterface {

    override fun getNewNumber() : Int {
        return round(Math.random() * 100).toInt()
    }

    override fun getNewName(): String {
        val choice = Random.nextInt(Names.names.size)
        return choice.toString() + " - " + Names.names[choice]
    }

}

interface MyRepoInterface {

    fun getNewNumber() : Int

    fun getNewName() : String
}

data object Names {
    val names : List<String> =
        listOf("Carrie", "Richard", "Jennifer", "Charles", "Carlos", "David", "Susan", "Winston")
}

Build your UI Passing the Repository

Finally, build the UI in the MainActivity. When creating an instance of the ViewModel, pass along the instance of the Repository.

class MainActivity : ComponentActivity() {

    lateinit var viewModel : MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val mainRepo : MainRepository = MainRepository()
        val factory = ViewModelFactory(mainRepo)
        viewModel = ViewModelProvider(
            this,
            factory
        )[MainViewModel::class.java]

        setContent {

            val data = viewModel.data.collectAsStateWithLifecycle().value

            SampleRepositoryInterfaceTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Column(modifier = Modifier.fillMaxSize().padding(innerPadding),
                        horizontalAlignment = Alignment.CenterHorizontally,
                        verticalArrangement = Arrangement.Center) {
                        Text(text = data)
                        Button(onClick = {
                            viewModel.processData()
                        }) {
                            Text(text = "Change Data")
                        }
                    }
                }
            }
        }
    }
}