I want to use my args.breweryName from my reviewActivity in my reviewViewModel but i don't know what's the best way to do it. I have tried using savestatehandle but i'm not experienced with that. I'm new to programming with kotlin so i would appreciate the help!
My ReviewActivity
class ReviewActivity : AppCompatActivity() {
val breweryText: TextView
get() = findViewById(R.id.breweryName)
private val args: ReviewActivityArgs by navArgs<ReviewActivityArgs>()
private var shownFragment: Fragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_review)
initView()
if (savedInstanceState == null) {
showFragment(ReviewFragment.newInstance())
}
breweryText.text = args.breweryName
}
fun test(): String {
return args.breweryName
}
private fun initView() {
val button : FloatingActionButton = findViewById(R.id.add)
showFragment(ReviewFragment.newInstance())
button.setOnClickListener { showSaveDialog() }
}
private fun showFragment(fragment: Fragment) {
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragmentHolder, fragment)
fragmentTransaction.commitNow()
shownFragment = fragment
}
private fun showSaveDialog() {
val dialogFragment: DialogFragment
val tag: String
dialogFragment = ReviewDialog.newInstance(null, null, args.breweryName)
tag = ReviewDialog.TAG_DIALOG_REVIEW_SAVE
dialogFragment.show(supportFragmentManager, tag)
}
My ReviewViewModel
class ReviewViewModel constructor(application: Application) : AndroidViewModel(application) {
private val reviewDao: ReviewDao = ReviewsDatabase.getDatabase(application).reviewDao()
private val userDao: UserDao = ReviewsDatabase.getDatabase(application).userDao()
val data = MutableLiveData<String>()
val reviewList: LiveData<List<Review>>
val userList: LiveData<List<User>>
fun data(item: String) {
data.value = item
}
init {
reviewList = reviewDao.allReviews
userList = userDao.allUsers
}
fun insert(vararg reviews: Review) {
reviewDao.insert(*reviews)
}
fun update(review: Review) {
reviewDao.update(review)
}
fun deleteAll() {
reviewDao.deleteAll()
}
}
I am developing new app and implemented shared preferences in my appModule.kt but when I run project I am getting following errors
Task :app:hiltJavaCompileDebug
C:\Users\Yodgorbek\RunTrackerApp\app\build\generated\hilt\component_sources\debug\com\example\runtrackerapp\BaseApplication_HiltComponents.java:136: error: [Dagger/Nullable] android.content.SharedPreferences is not nullable, but is being provided by #Singleton #Provides #org.jetbrains.annotations.Nullable android.content.SharedPreferences com.example.runtrackerapp.di.AppModule.provideSharedPreferences(#dagger.hilt.android.qualifiers.ApplicationContext android.content.Context)
public abstract static class SingletonC implements BaseApplication_GeneratedInjector,
^
android.content.SharedPreferences is injected at
com.example.runtrackerapp.di.AppModule.provideWeight(sharedPref)
java.lang.Float is injected at
com.example.runtrackerapp.ui.fragments.TrackingFragment.setWeight(<set-?>)
com.example.runtrackerapp.ui.fragments.TrackingFragment is injected at
com.example.runtrackerapp.ui.fragments.TrackingFragment_GeneratedInjector.injectTrackingFragment(com.example.runtrackerapp.ui.fragments.TrackingFragment) [com.example.runtrackerapp.BaseApplication_HiltComponents.SingletonC ? com.example.runtrackerapp.BaseApplication_HiltComponents.ActivityRetainedC ? com.example.runtrackerapp.BaseApplication_HiltComponents.ActivityC ? com.example.runtrackerapp.BaseApplication_HiltComponents.FragmentC]
C:\Users\Yodgorbek\RunTrackerApp\app\build\generated\hilt\component_sources\debug\com\example\runtrackerapp\BaseApplication_HiltComponents.java:136: error: [Dagger/Nullable] android.content.SharedPreferences is not nullable, but is being provided by #Singleton #Provides #org.jetbrains.annotations.Nullable android.content.SharedPreferences com.example.runtrackerapp.di.AppModule.provideSharedPreferences(#dagger.hilt.android.qualifiers.ApplicationContext android.content.Context)
public abstract static class SingletonC implements BaseApplication_GeneratedInjector,
^
android.content.SharedPreferences is injected at
com.example.runtrackerapp.ui.fragments.SetupFragment.sharedPref
com.example.runtrackerapp.ui.fragments.SetupFragment is injected at
com.example.runtrackerapp.ui.fragments.SetupFragment_GeneratedInjector.injectSetupFragment(com.example.runtrackerapp.ui.fragments.SetupFragment) [com.example.runtrackerapp.BaseApplication_HiltComponents.SingletonC ? com.example.runtrackerapp.BaseApplication_HiltComponents.ActivityRetainedC ? com.example.runtrackerapp.BaseApplication_HiltComponents.ActivityC ? com.example.runtrackerapp.BaseApplication_HiltComponents.FragmentC]
C:\Users\Yodgorbek\RunTrackerApp\app\build\generated\hilt\component_sources\debug\com\example\runtrackerapp\BaseApplication_HiltComponents.java:136: error: [Dagger/Nullable] android.content.SharedPreferences is not nullable, but is being provided by #Singleton #Provides #org.jetbrains.annotations.Nullable android.content.SharedPreferences com.example.runtrackerapp.di.AppModule.provideSharedPreferences(#dagger.hilt.android.qualifiers.ApplicationContext android.content.Context)
public abstract static class SingletonC implements BaseApplication_GeneratedInjector,
^
android.content.SharedPreferences is injected at
com.example.runtrackerapp.di.AppModule.provideFirstTimeToggle(sharedPref)
java.lang.Boolean is injected at
com.example.runtrackerapp.ui.fragments.SetupFragment.setFirstAppOpen(<set-?>)
com.example.runtrackerapp.ui.fragments.SetupFragment is injected at
com.example.runtrackerapp.ui.fragments.SetupFragment_GeneratedInjector.injectSetupFragment(com.example.runtrackerapp.ui.fragments.SetupFragment) [com.example.runtrackerapp.BaseApplication_HiltComponents.SingletonC ? com.example.runtrackerapp.BaseApplication_HiltComponents.ActivityRetainedC ? com.example.runtrackerapp.BaseApplication_HiltComponents.ActivityC ? com.example.runtrackerapp.BaseApplication_HiltComponents.FragmentC]
3 errors
below my appModule.kt
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Singleton
#Provides
fun provideRunningDatabase(
#ApplicationContext app: Context
) = Room.databaseBuilder(
app,
RunningDatabase::class.java,
RUNNING_DATABASE_NAME
).build()
#Singleton
#Provides
fun provideRunDao(db: RunningDatabase) = db.getRunDao()
#Singleton
#Provides
fun provideSharedPreferences(#ApplicationContext app: Context): SharedPreferences? {
val sharedPreferences = app.getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE)
return sharedPreferences
}
#Singleton
#Provides
fun provideName(sharedPref: SharedPreferences) = sharedPref.getString(KEY_NAME, "") ?: ""
#Singleton
#Provides
fun provideWeight(sharedPref: SharedPreferences) = sharedPref.getFloat(KEY_WEIGHT, 80f)
#Singleton
#Provides
fun provideFirstTimeToggle(sharedPref: SharedPreferences) =
sharedPref.getBoolean(KEY_FIRST_TIME_TOGGLE, true)
}
below SetupFragment.kt
#AndroidEntryPoint
class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null
private val binding get() = _binding!!
#Inject
lateinit var sharedPref: SharedPreferences
#set:Inject
var isFirstAppOpen = true
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// inflate the layout and bind to the _binding
_binding = FragmentSetupBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if(!isFirstAppOpen){
val navOptions = NavOptions.Builder()
.setPopUpTo(R.id.setupFragment, true)
.build()
findNavController().navigate(
R.id.action_setupFragment_to_runFragment,
savedInstanceState,
navOptions)
}
binding.tvContinue.setOnClickListener {
val success = writePersonalDataToSharedPref()
if (success){
findNavController().navigate(R.id.action_setupFragment_to_runFragment)
}else{
Snackbar.make(requireView(), "Please enter all the fields", Snackbar.LENGTH_SHORT).show()
}
}
}
private fun writePersonalDataToSharedPref(): Boolean {
val name = binding.etName.text.toString()
val weight = binding.etWeight.text.toString()
if(name.isEmpty() || weight.isEmpty()) {
return false
}
sharedPref.edit()
.putString(KEY_NAME, name)
.putFloat(KEY_WEIGHT, weight.toFloat())
.putBoolean(KEY_FIRST_TIME_TOGGLE, false)
.apply()
val toolbarText = "Let's go, $name!"
(requireActivity() as ActivityMainBinding).tvToolbarTitle.text = toolbarText
return true
}
}
below TrackingFragment.kt
#AndroidEntryPoint
class TrackingFragment : Fragment(R.layout.fragment_tracking) {
private val viewModel: MainViewModel by viewModels()
private var isTracking = false
private var pathPoints = mutableListOf<Polyline>()
private var map: GoogleMap? = null
private var curTimeInMillis = 0L
private var _binding: FragmentTrackingBinding? = null
private val binding get() = _binding!!
private var menu: Menu? = null
#set:Inject
var weight = 80f
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
setHasOptionsMenu(true)
_binding = FragmentTrackingBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.mapView.onCreate(savedInstanceState)
binding.btnToggleRun.setOnClickListener {
toggleRun()
}
binding.btnFinishRun.setOnClickListener {
zoomToSeeWholeTrack()
endRunAndSaveToDb()
}
binding.mapView.getMapAsync {
map = it
addAllPolylines()
}
subscribeToObservers()
}
private fun subscribeToObservers() {
TrackingService.isTracking.observe(viewLifecycleOwner, Observer {
updateTracking(it)
})
TrackingService.pathPoints.observe(viewLifecycleOwner, Observer {
pathPoints = it
addLatestPolyline()
moveCameraToUser()
})
TrackingService.timeRunInMillis.observe(viewLifecycleOwner, Observer {
curTimeInMillis = it
val formattedTime = TrackingUtility.getFormattedStopWatchTime(curTimeInMillis, true)
binding.tvTimer.text = formattedTime
})
}
private fun toggleRun() {
if(isTracking) {
menu?.getItem(0)?.isVisible = true
sendCommandToService(ACTION_PAUSE_SERVICE)
} else {
sendCommandToService(ACTION_START_OR_RESUME_SERVICE)
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.toolbar_tracking_menu, menu)
this.menu = menu
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
if(curTimeInMillis > 0L) {
this.menu?.getItem(0)?.isVisible = true
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId) {
R.id.miCancelTracking -> {
showCancelTrackingDialog()
}
}
return super.onOptionsItemSelected(item)
}
private fun showCancelTrackingDialog() {
val dialog = MaterialAlertDialogBuilder(requireContext(), R.style.AlertDialogTheme)
.setTitle("Cancel the Run?")
.setMessage("Are you sure to cancel the current run and delete all its data?")
.setIcon(R.drawable.ic_delete)
.setPositiveButton("Yes") { _, _ ->
stopRun()
}
.setNegativeButton("No") { dialogInterface, _ ->
dialogInterface.cancel()
}
.create()
dialog.show()
}
private fun stopRun() {
sendCommandToService(ACTION_STOP_SERVICE)
findNavController().navigate(R.id.action_trackingFragment_to_runFragment)
}
private fun updateTracking(isTracking: Boolean) {
this.isTracking = isTracking
if(!isTracking) {
binding.btnToggleRun.text = "Start"
binding.btnFinishRun.visibility = View.VISIBLE
} else {
binding.btnToggleRun.text = "Stop"
menu?.getItem(0)?.isVisible = true
binding.btnFinishRun.visibility = View.GONE
}
}
private fun moveCameraToUser() {
if(pathPoints.isNotEmpty() && pathPoints.last().isNotEmpty()) {
map?.animateCamera(
CameraUpdateFactory.newLatLngZoom(
pathPoints.last().last(),
MAP_ZOOM
)
)
}
}
private fun zoomToSeeWholeTrack(){
val bounds = LatLngBounds.builder()
for(polyline in pathPoints){
for(pos in polyline){
bounds.include(pos)
}
}
map?.moveCamera(
CameraUpdateFactory.newLatLngBounds(
bounds.build(),
binding.mapView.width,
binding.mapView.height,
(binding.mapView.height * 0.05f).toInt()
)
)
}
private fun endRunAndSaveToDb(){
map?.snapshot { bmp ->
var distanceInMeters = 0
for (polyline in pathPoints){
distanceInMeters+= TrackingUtility.calculatePolylineLength(polyline).toInt()
}
val avgSpeed = round((distanceInMeters / 1000f) / (curTimeInMillis / 1000f / 60 / 60) * 10) / 10f
val dataTimestamp = Calendar.getInstance().timeInMillis
val caloriesBurned = ((distanceInMeters / 1000f) * weight).toInt()
val run = Run(bmp,dataTimestamp, avgSpeed,distanceInMeters, curTimeInMillis, caloriesBurned)
viewModel.insertRun(run)
Snackbar.make(
requireActivity().findViewById(R.id.rootView),
"Run saved successfully",
Snackbar.LENGTH_LONG
).show()
stopRun()
}
}
private fun addAllPolylines() {
for(polyline in pathPoints) {
val polylineOptions = PolylineOptions()
.color(POLYLINE_COLOR)
.width(POLYLINE_WIDTH)
.addAll(polyline)
map?.addPolyline(polylineOptions)
}
}
private fun addLatestPolyline() {
if(pathPoints.isNotEmpty() && pathPoints.last().size > 1) {
val preLastLatLng = pathPoints.last()[pathPoints.last().size - 2]
val lastLatLng = pathPoints.last().last()
val polylineOptions = PolylineOptions()
.color(POLYLINE_COLOR)
.width(POLYLINE_WIDTH)
.add(preLastLatLng)
.add(lastLatLng)
map?.addPolyline(polylineOptions)
}
}
private fun sendCommandToService(action: String) =
Intent(requireContext(), TrackingService::class.java).also {
it.action = action
requireContext().startService(it)
}
override fun onResume() {
super.onResume()
binding.mapView?.onResume()
}
override fun onStart() {
super.onStart()
binding.mapView?.onStart()
}
override fun onStop() {
super.onStop()
binding.mapView?.onStop()
}
override fun onPause() {
super.onPause()
binding.mapView?.onPause()
}
override fun onLowMemory() {
super.onLowMemory()
binding.mapView?.onLowMemory()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
binding.mapView?.onSaveInstanceState(outState)
}
}
I want to know where I am making mistake I have followed all stackoverflow answer it did not solve my issue
A provider should not be nullable in your case provideSharedPreferences..
Remove Nullble from return type.
#Singleton
#Provides
fun provideSharedPreferences(#ApplicationContext app: Context): SharedPreferences {
val sharedPreferences = app.getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE)
return sharedPreferences
}
I am trying to execute a method(fun onCreateViewModel()) in viewModel whenever the onCreate of the Activity is invoked. But the method is not getting executed.
ViewModel
class MainActivityViewModel(startingCount : Int) : ViewModel(), LifecycleObserver {
var count = 0
init {
count = startingCount
}
fun getCurrentCount(): Int{
return count
}
fun getUpdatedCount(): Int {
count ++
return count
}
#OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public fun onCreateViewModel(){
Log.i("Jts"," ViewModel created")
}
}
Activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var user = User("Jeffin T", "26")
private lateinit var mainActivityViewModel: MainActivityViewModel
private lateinit var mainActivityViewModelFactory: MainActivityViewModelFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// mainActivityViewModel = ViewModelProviders.of()
mainActivityViewModelFactory = MainActivityViewModelFactory(123)
mainActivityViewModel = ViewModelProviders.of(this, mainActivityViewModelFactory)
.get(MainActivityViewModel::class.java)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.tvName.text = mainActivityViewModel.getCurrentCount().toString()
binding.user = user
binding.btnName.setOnClickListener {
binding.tvName.text = mainActivityViewModel.getUpdatedCount().toString()
}
}
private fun displayGreeting() {
binding.apply { tvName.setText("Hello! " + user?.name) }
}
}
You have to add the viewmodel as an observer of the lifecycle of your activity. You need to add this in your Activity
getLifecycle().addObserver(viewModel)
More info here
I have following project in Github : https://github.com/AliRezaeiii/TMDb-Paging
I have to postDelay calling methods in my ViewModel since datasource is not initialized :
abstract class DetailViewModel(private val item: TmdbItem) : BaseViewModel() {
private val handler = Handler(Looper.getMainLooper())
val trailers: ObservableList<Video> = ObservableArrayList()
val isTrailersVisible = ObservableBoolean(false)
private val _cast = MutableLiveData<List<Cast>>()
val cast: LiveData<List<Cast>> = _cast
val isCastVisible = ObservableBoolean(false)
init {
handler.postDelayed({
showTrailers()
showCast()
}, 100)
}
protected abstract fun getTrailers(id: Int): Observable<List<Video>>
protected abstract fun getCast(id: Int): Observable<List<Cast>>
private fun showTrailers() {
EspressoIdlingResource.increment() // App is busy until further notice
compositeDisposable.add(getTrailers(item.id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally {
if (!EspressoIdlingResource.getIdlingResource().isIdleNow) {
EspressoIdlingResource.decrement() // Set app as idle.
}
}
.subscribe({ videos ->
if (videos.isNotEmpty()) {
isTrailersVisible.set(true)
}
with(trailers) {
clear()
addAll(videos)
}
}
) { throwable -> Timber.e(throwable) })
}
private fun showCast() {
EspressoIdlingResource.increment() // App is busy until further notice
compositeDisposable.add(getCast(item.id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally {
if (!EspressoIdlingResource.getIdlingResource().isIdleNow) {
EspressoIdlingResource.decrement() // Set app as idle.
}
}
.subscribe({ cast ->
if (cast.isNotEmpty()) {
isCastVisible.set(true)
}
this._cast.postValue(cast)
}
) { throwable -> Timber.e(throwable) })
}
}
And here is my Fragment :
abstract class DetailFragment<T : TmdbItem>
: BaseDaggerFragment(), CastClickCallback {
protected abstract fun getViewModel(): DetailViewModel
protected abstract fun getLayoutId(): Int
protected abstract fun initViewBinding(root: View): ViewDataBinding
protected abstract fun getTmdbItem(): T
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val viewModel = getViewModel()
val root = inflater.inflate(getLayoutId(), container, false)
initViewBinding(root).apply {
setVariable(BR.vm, viewModel)
lifecycleOwner = viewLifecycleOwner
}
with(root) {
with(activity as AppCompatActivity) {
setupActionBar(details_toolbar) {
setDisplayShowTitleEnabled(false)
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
}
summary_label.visibleGone(getTmdbItem().overview.trim().isNotEmpty())
// Make the MotionLayout draw behind the status bar
details_motion.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
summary.setOnClickListener {
val maxLine = resources.getInteger(R.integer.max_lines)
summary.maxLines = if (summary.maxLines > maxLine) maxLine else Int.MAX_VALUE
}
viewModel.cast.observe(viewLifecycleOwner, Observer {
it?.apply {
val adapter = CastAdapter(it, this#DetailFragment)
cast_list.apply {
setHasFixedSize(true)
cast_list.adapter = adapter
}
}
})
with(details_rv) {
postDelayed({ scrollTo(0, 0) }, 100)
}
}
return root
}
}
And BaseDaggerFragment :
open class BaseDaggerFragment : DaggerFragment() {
#Inject
lateinit var dataSource: RemoteDataSource
}
Could be any better solution than :
init {
handler.postDelayed({
showTrailers()
showCast()
}, 100)
}
You can lazy initialize like this way
private val users:MutableLiveData<List<Cast>> by lazy {
MutableLiveData().also {
showTrailers()
showCast()
}
}
more details refer ViewModel
I am using helper classes with retry logic which is used throughout my app. I was wondering if there is a way I can combine the handler and callback classes into one instead of having 2 different classes one for callback and one for handler. Here's my code as follows:
Retryhandler:
abstract class RetryHandler(private val totalRetries: Int = 0, private val delayMillis : Long = 0) : Handler() {
private var retryCount: Int = 0
fun retry(): Boolean {
return if (retryCount++ < totalRetries) {
if (delayMillis > 0) {
postDelayed({ onRetry(retryCount) }, delayMillis)
} else {
onRetry(retryCount)
true
}
} else false
}
abstract fun onRetry(retryCount: Int)
}
Retrycallback:
abstract class RetryableCallback(totalRetries: Int = 0, delayMillis : Long = 0)
: RetryHandler(totalRetries, delayMillis), MyCallback {
override fun handleTransactionCompleted() {
if (!onCompleted()) {
if (!retry()) {
onFailed(null)
}
}
}
override fun handleTransactionFailed(e: MyException?) {
if (!retry()) {
onFailed(e)
}
}
abstract fun onCompleted(): Boolean
abstract fun onFailed(e: MyException? = null)
}
Here's how I am using them in my code:
private val newCallback = object: RetryableCallback(5, 5000) {
override fun onRetry(retryCount: Int) {
....}
override fun onCompleted(): Boolean {
}
}
Any ideas ?
Well, as long as I don't fully understand the purpose, let's say like this:
abstract class RetriableCallbackHandler(private val totalRetries: Int = 0, private val delayMillis : Long = 0) : Handler(), MyCallback {
private var retryCount: Int = 0
fun retry(): Boolean {
return if (retryCount++ < totalRetries) {
if (delayMillis > 0) {
postDelayed({ onRetry(retryCount) }, delayMillis)
} else {
onRetry(retryCount)
true
}
} else false
}
abstract fun onRetry(retryCount: Int)
override fun handleTransactionCompleted() {
if (!onCompleted()) {
if (!retry()) {
onFailed(null)
}
}
}
override fun handleTransactionFailed(e: MyException?) {
if (!retry()) {
onFailed(e)
}
}
abstract fun onCompleted(): Boolean
abstract fun onFailed(e: MyException? = null)
}