Merge pull request #3 from mattintech/feature/depInj

adding DepedencyInjnection with HILT
This commit is contained in:
2025-07-03 21:14:56 -04:00
committed by GitHub
14 changed files with 103 additions and 55 deletions

View File

@@ -2,6 +2,12 @@ plugins {
id("com.android.application") id("com.android.application")
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
id("androidx.navigation.safeargs.kotlin") id("androidx.navigation.safeargs.kotlin")
id("kotlin-kapt")
id("com.google.dagger.hilt.android")
}
kapt {
correctErrorTypes = true
} }
android { android {
@@ -11,6 +17,12 @@ android {
lint { lint {
abortOnError = false abortOnError = false
} }
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
defaultConfig { defaultConfig {
applicationId = "com.mattintech.lchat" applicationId = "com.mattintech.lchat"
@@ -50,6 +62,7 @@ android {
} }
buildFeatures { buildFeatures {
viewBinding = true viewBinding = true
buildConfig = true
} }
} }
@@ -66,6 +79,10 @@ dependencies {
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6") implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
implementation("androidx.navigation:navigation-ui-ktx:2.7.6") implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
// Hilt dependencies
implementation("com.google.dagger:hilt-android:2.50")
kapt("com.google.dagger:hilt-compiler:2.50")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

View File

@@ -11,4 +11,17 @@
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
# Keep Application class
-keep class com.mattintech.lchat.LChatApplication { *; }
# Hilt rules
-keep class dagger.hilt.** { *; }
-keep class javax.inject.** { *; }
-keep class * extends dagger.hilt.android.internal.managers.ViewComponentManager { *; }
# Keep all @HiltAndroidApp, @AndroidEntryPoint, @HiltViewModel annotated classes
-keep @dagger.hilt.android.HiltAndroidApp class * { *; }
-keep @dagger.hilt.android.AndroidEntryPoint class * { *; }
-keep @dagger.hilt.android.lifecycle.HiltViewModel class * { *; }

View File

@@ -15,6 +15,7 @@
<uses-feature android:name="android.hardware.wifi.aware" android:required="true" /> <uses-feature android:name="android.hardware.wifi.aware" android:required="true" />
<application <application
android:name=".LChatApplication"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"

View File

@@ -0,0 +1,11 @@
package com.mattintech.lchat
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class LChatApplication : Application() {
override fun onCreate() {
super.onCreate()
}
}

View File

@@ -11,7 +11,9 @@ import androidx.core.content.ContextCompat
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.mattintech.lchat.databinding.ActivityMainBinding import com.mattintech.lchat.databinding.ActivityMainBinding
import com.mattintech.lchat.utils.LOG_PREFIX import com.mattintech.lchat.utils.LOG_PREFIX
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
companion object { companion object {

View File

@@ -0,0 +1,23 @@
package com.mattintech.lchat.di
import android.content.Context
import com.mattintech.lchat.network.WifiAwareManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideWifiAwareManager(
@ApplicationContext context: Context
): WifiAwareManager {
return WifiAwareManager(context)
}
}

View File

@@ -3,24 +3,19 @@ package com.mattintech.lchat.repository
import android.content.Context import android.content.Context
import com.mattintech.lchat.data.Message import com.mattintech.lchat.data.Message
import com.mattintech.lchat.network.WifiAwareManager import com.mattintech.lchat.network.WifiAwareManager
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import java.util.UUID import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
class ChatRepository private constructor(context: Context) { @Singleton
class ChatRepository @Inject constructor(
companion object { @ApplicationContext private val context: Context
@Volatile ) {
private var INSTANCE: ChatRepository? = null
fun getInstance(context: Context): ChatRepository {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: ChatRepository(context.applicationContext).also { INSTANCE = it }
}
}
}
private val wifiAwareManager = WifiAwareManager(context) private val wifiAwareManager = WifiAwareManager(context)
@@ -140,7 +135,7 @@ class ChatRepository private constructor(context: Context) {
private fun startConnectionMonitoring() { private fun startConnectionMonitoring() {
connectionCheckJob?.cancel() connectionCheckJob?.cancel()
connectionCheckJob = GlobalScope.launch { connectionCheckJob = CoroutineScope(Dispatchers.IO).launch {
while (isActive) { while (isActive) {
delay(5000) // Check every 5 seconds delay(5000) // Check every 5 seconds
val timeSinceLastActivity = System.currentTimeMillis() - lastActivityTime val timeSinceLastActivity = System.currentTimeMillis() - lastActivityTime

View File

@@ -6,7 +6,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -17,9 +17,10 @@ import com.mattintech.lchat.repository.ChatRepository
import com.mattintech.lchat.ui.adapters.MessageAdapter import com.mattintech.lchat.ui.adapters.MessageAdapter
import com.mattintech.lchat.utils.LOG_PREFIX import com.mattintech.lchat.utils.LOG_PREFIX
import com.mattintech.lchat.viewmodel.ChatViewModel import com.mattintech.lchat.viewmodel.ChatViewModel
import com.mattintech.lchat.viewmodel.ViewModelFactory import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@AndroidEntryPoint
class ChatFragment : Fragment() { class ChatFragment : Fragment() {
companion object { companion object {
@@ -30,7 +31,7 @@ class ChatFragment : Fragment() {
private val binding get() = _binding!! private val binding get() = _binding!!
private val args: ChatFragmentArgs by navArgs() private val args: ChatFragmentArgs by navArgs()
private lateinit var viewModel: ChatViewModel private val viewModel: ChatViewModel by viewModels()
private lateinit var messageAdapter: MessageAdapter private lateinit var messageAdapter: MessageAdapter
override fun onCreateView( override fun onCreateView(
@@ -47,8 +48,6 @@ class ChatFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
Log.d(TAG, "onViewCreated - room: ${args.roomName}, user: ${args.userName}, isHost: ${args.isHost}") Log.d(TAG, "onViewCreated - room: ${args.roomName}, user: ${args.userName}, isHost: ${args.isHost}")
val factory = ViewModelFactory(requireContext())
viewModel = ViewModelProvider(this, factory)[ChatViewModel::class.java]
viewModel.initialize(args.roomName, args.userName, args.isHost) viewModel.initialize(args.roomName, args.userName, args.isHost)
setupUI() setupUI()

View File

@@ -7,16 +7,17 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.mattintech.lchat.R import com.mattintech.lchat.R
import com.mattintech.lchat.databinding.FragmentLobbyBinding import com.mattintech.lchat.databinding.FragmentLobbyBinding
import com.mattintech.lchat.viewmodel.LobbyEvent import com.mattintech.lchat.viewmodel.LobbyEvent
import com.mattintech.lchat.viewmodel.LobbyState import com.mattintech.lchat.viewmodel.LobbyState
import com.mattintech.lchat.viewmodel.LobbyViewModel import com.mattintech.lchat.viewmodel.LobbyViewModel
import com.mattintech.lchat.viewmodel.ViewModelFactory
import com.mattintech.lchat.utils.LOG_PREFIX import com.mattintech.lchat.utils.LOG_PREFIX
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class LobbyFragment : Fragment() { class LobbyFragment : Fragment() {
companion object { companion object {
@@ -26,7 +27,7 @@ class LobbyFragment : Fragment() {
private var _binding: FragmentLobbyBinding? = null private var _binding: FragmentLobbyBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private lateinit var viewModel: LobbyViewModel private val viewModel: LobbyViewModel by viewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@@ -42,9 +43,6 @@ class LobbyFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
Log.d(TAG, "onViewCreated") Log.d(TAG, "onViewCreated")
val factory = ViewModelFactory(requireContext())
viewModel = ViewModelProvider(this, factory)[LobbyViewModel::class.java]
setupUI() setupUI()
observeViewModel() observeViewModel()
} }

View File

@@ -6,11 +6,13 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.mattintech.lchat.data.Message import com.mattintech.lchat.data.Message
import com.mattintech.lchat.repository.ChatRepository import com.mattintech.lchat.repository.ChatRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID import java.util.UUID
import javax.inject.Inject
sealed class ChatState { sealed class ChatState {
object Connected : ChatState() object Connected : ChatState()
@@ -18,7 +20,8 @@ sealed class ChatState {
data class Error(val message: String) : ChatState() data class Error(val message: String) : ChatState()
} }
class ChatViewModel( @HiltViewModel
class ChatViewModel @Inject constructor(
private val chatRepository: ChatRepository private val chatRepository: ChatRepository
) : ViewModel() { ) : ViewModel() {

View File

@@ -5,7 +5,9 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.mattintech.lchat.repository.ChatRepository import com.mattintech.lchat.repository.ChatRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
sealed class LobbyState { sealed class LobbyState {
object Idle : LobbyState() object Idle : LobbyState()
@@ -23,7 +25,8 @@ sealed class LobbyEvent {
data class ShowError(val message: String) : LobbyEvent() data class ShowError(val message: String) : LobbyEvent()
} }
class LobbyViewModel( @HiltViewModel
class LobbyViewModel @Inject constructor(
private val chatRepository: ChatRepository private val chatRepository: ChatRepository
) : ViewModel() { ) : ViewModel() {

View File

@@ -1,24 +0,0 @@
package com.mattintech.lchat.viewmodel
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.mattintech.lchat.repository.ChatRepository
class ViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
private val chatRepository by lazy { ChatRepository.getInstance(context) }
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return when {
modelClass.isAssignableFrom(LobbyViewModel::class.java) -> {
LobbyViewModel(chatRepository) as T
}
modelClass.isAssignableFrom(ChatViewModel::class.java) -> {
ChatViewModel(chatRepository) as T
}
else -> throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
}
}

View File

@@ -11,5 +11,6 @@ buildscript {
plugins { plugins {
id("com.android.application") version "8.9.1" apply false id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "1.9.0" apply false id("org.jetbrains.kotlin.android") version "1.9.22" apply false
id("com.google.dagger.hilt.android") version "2.50" apply false
} }

View File

@@ -1,6 +1,12 @@
# Project-wide Gradle settings. # Project-wide Gradle settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
android.useAndroidX=true android.useAndroidX=true
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
android.defaults.buildfeatures.buildconfig=true android.nonFinalResIds=false
android.nonFinalResIds=false
# Kapt configuration for better performance
kapt.use.worker.api=true
kapt.incremental.apt=true
# Hilt configuration
dagger.hilt.android.internal.disableAndroidSuperclassValidation=true