How do I pass a function from a ViewModel to my scaffold's floating action button in Jetpack Compose? - mvvm

I'm building an Android app using purely Jetpack Compose. My entire application is wrapped under one scaffold, and has a ViewModel for each "screen" (which are composables) in my app. Because of that, I have some conditional statements in my scaffold to determine the floating action button (FAB) based on the route. However, one of the FABs needs access to a function in a ViewModel, which is only created when I navigate to the route that holds that composable, and I'm at a loss on the best way to give the FAB access to that viewmodel function.
Take the following example (based off my code), and note the FAB for the route "route3".
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
#Composable
fun MyApp() {
val navController = rememberNavController()
val backstackEntry = navController.currentBackStackEntryAsState()
val scaffoldState = rememberScaffoldState()
Surface(color = MaterialTheme.colors.background) {
Scaffold(
topBar = { ... },
bottomBar = { ... },
floatingActionButton = {
when (backstackEntry.value?.destination?.route) {
"route2" -> FAB2(navController)
"route3" -> FAB3(navController) // Needs to access function from viewModel3
}
},
scaffoldState = scaffoldState,
) {
MyNavHost(navController, scaffoldState)
}
}
}
#Composable
fun MyNavHost(navController: NavHostController, scaffoldState: ScaffoldState) {
NavHost(
navController = navController,
startDestination = "route1"
) {
composable("route1") { Destination1() }
composable("route2") { Destination2() }
composable("route3") { Destination3() }
}
}
#Composable
fun Destination1() { ...}
#Composable
fun Destination2() { ... }
#Composable
fun Destination3() {
val viewModel3: CustomViewModel = hiltViewModel()
Screen3(viewModel3)
}
}
So my main question is, if the FAB3 variable needs to access a function from viewModel3, how would I go about doing it?

I decided to just switch over to using a scaffold for every screen.
Honestly, this makes managing routes a lot easier, because in the single scaffold scenario, it was getting difficult managing all of the possible routes for things like the TopBar and FAB in large when() blocks.
But if anyone has a solution to the original question that would be appreciated!

Related

Constraintlayout Barrier setType not work

i have a Barrier in activity_constraint_layout, and want change it's type based some data from network, but setType(int type) is not work.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_constraint_layout)
clContainer = findViewById(R.id.clContainer)
// worked here
val barrier = findViewById<Barrier>(R.id.barrier)
barrier.type = Barrier.START
clContainer.setOnClickListener {
// do not work here
barrier.type = Barrier.END
}
}
The statement barrier.type = Barrier.END executes the following withing the Barrier class:
public void setType(int type) {
mIndicatedType = type;
}
As you can see, it only sets the value of a variable. You also need to request a layout as follows:
clContainer.setOnClickListener {
// do not work here
barrier.type = Barrier.END
barrier.requestLayout()
}
Once you do this, you should see the effect you want.

android mvvm and livedata: list not observed in activity

I am developing an android app that has an activity with a recyclerview and an add button. When add button is clicked, a FragmentDialog is launched. the user enters the name of the book which will be stored in a greendao database. The list of books is displayed in the recyclerview.
I am using mvvm and livedata . the problem is that after adding the book in DialogFragment, the list is not updated, although the list is wrapped in livedata which is observed in activity.
DialogFragment:
// repository
val repository = Injection.provideRepository()
// viewmodel
val factory = CreateBookViewModelFactory(repository)
val viewModel =
ViewModelProvider(this, factory).get(BookViewModel::class.java)
builder.setView(binding.root)
// Add action buttons
.setPositiveButton(R.string.add,
DialogInterface.OnClickListener { dialog, id ->
// create book
book = Book(null, binding.name)
viewModel.onAddBook(book)
})
.setNegativeButton(R.string.cancel,
DialogInterface.OnClickListener { dialog, id ->
getDialog()?.cancel()
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
viewModel:
class BookViewModel(val repository: Repository) : ViewModel() {
// Create a LiveData
val _books = MutableLiveData<List<Book>>()
val books = repository.books
fun init() :MutableLiveData<List<Book>> {
_books.postValue(books)
return _books
}
}
activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_books)
binding.setLifecycleOwner(this)
binding.recyclerview.adapter = adapter
binding.recyclerview.setHasFixedSize(false)
// repository
val repository = Injection.provideRepository()
val factory = CreateBookViewModelFactory(repository)
val viewModel =
ViewModelProvider(this, factory).get(BookViewModel::class.java)
viewModel.init().observe(this, Observer {
it?.let {
adapter.submitList(Mapper.mapToBookListDTO(it))
}
})
binding.addReturn.setOnClickListener { view ->
val createFragment: DialogFragment = BookDialog()
createFragment.show(supportFragmentManager, "new Book")
}
}
When I add a new book and click the add button, the list is not updated. But when I reopen the activity the list of books get updated, it means the list is not observed for changes.
I am using the same viewModel for the activity and the DialogFragment. I also tried using seperate viewmodels for each, but same result.
Thanks a lot for your help!

How to make an Android widget

Added a widget to a project and edited the result to show a simple counter -- or so I thought.
class TestWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
val prefs: SharedPreferences = context.getSharedPreferences("TestWidget", 0)
val n:Int = prefs.getInt("n", 1)
val e = prefs.edit()
e.putInt("n", n + 1)
e.apply()
e.commit()
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId, n)
}
}
override fun onEnabled(context: Context) {
// Enter relevant functionality for when the first widget is created
}
override fun onDisabled(context: Context) {
// Enter relevant functionality for when the last widget is disabled
}
}
internal fun updateAppWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
value:Int
) {
val widgetText = context.getString(R.string.appwidget_text)
// Construct the RemoteViews object
val views = RemoteViews(context.packageName, R.layout.test_widget)
views.setTextViewText(R.id.appwidget_text, value.toString())
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}
In test_widget_info.xml I changed updatePeriodMillis to 500.
I can add the widget to the home screen, but neither updateAppWidget nor onUpdate are never called.
However something must update the widget because the TextView has its text set to EXAMPLE but when running it is changed to Problem loading widget.
Is it possible to make this work? Is it possible to make a widget? Is this documented anywhere?
It can't update more than once every 30 minutes.
https://developer.android.com/reference/android/appwidget/AppWidgetProviderInfo.html#updatePeriodMillis
Workarounds suggest either a Service or a Timer, but do not explain how.

How to update activity views from a fragment recyclerView

I have MainActivity which has AppBar (with a "cart" TextView) and FrameLayout (with Fragment which contain a RecyclerView). This RecyclerView has a Button to increase Cart Value count. And I want that count to update Cart value in MainActivity.
I found this one but without full explanation "Updating views of Activity or Fragment from a RecyclerView adapter".
No better solution than this ViewModel
First of all add this dependency in build.gradle : implementation 'android.arch.lifecycle:extensions:1.1.1'
Your ViewModel class will look like this :
class MyViewModel: ViewModel() {
val mCartInfo: MutableLiveData<Int> = MutableLiveData()
private var cnt = 0
fun addToCartCounter() {
//TODO Apply your logic here
mCartInfo.value = cnt++
}
}
What is LiveData? See here
After that you've to initialize your ViewModel and observe the changed data, all these things are going to be done in your AppBar's Activity :
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val myViewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
//These will observe any changes happen in ViewModel's MutableLiveData(it'll directly jump inside Observer)
//Observer is main important thing
myViewModel.mCartInfo.observe(this, Observer {
//tvCartCounter is Toolbar's TextView
tvCartCounter.text = it.toString()
})
}
}
Get ViewModel in your Fragment and your button click which calls a ViewModel's method:
class BlankFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val rootView = inflater.inflate(R.layout.fragment_blank, container, false)
//Get your Activity's ViewModel
val myViewModel = ViewModelProviders.of(requireActivity()).get(MyViewModel::class.java)
//Button click
rootView.btnAddToCart.setOnClickListener {
//It'll call ViewModel's method
myViewModel.addToCartCounter()
}
return rootView
}
}
Whenever addToCartCounter() calls on your button click it'll add value in MutableLiveData soon after it'll triggered your observer.

Native Dialog for location setting on Flutter

Is there a way to implement a Dialog for Location setting like the image below which gets triggered when app requires GPS location and doesn't find. Hitting OK will right away turn on the system GPS. This seems more convenient for users instead of taking them to location and manually turn on.
Is it possible to implement such thing in Flutter?
Expanded View of dialog:
Credits to Rajesh, as answered here. The plugin lets you add this native dialog for a quick location setting.
The implementation is quite simple as this:
import 'package:location/location.dart';
var location = Location();
Future _checkGps() async {
if(!await location.serviceEnabled()){
location.requestService();
}
}
#override
void initState() {
super.initState();
_checkGps();
}
In Kotlin, try this code:
class HomeActivity : AppCompatActivity() {
private val requestLocation = 199
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableLoc()
}
private fun enableLoc() {
val mLocationRequest = LocationRequest.create()
mLocationRequest.interval = 10000
mLocationRequest.fastestInterval = 5000
// mLocationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
val builder = LocationSettingsRequest.Builder()
.addLocationRequest(mLocationRequest)
val client = LocationServices.getSettingsClient(this)
val task =
client.checkLocationSettings(builder.build())
task.addOnSuccessListener(this) {
// All location settings are satisfied. The client can initialize
// location requests here.
// ...
}
task.addOnFailureListener(this) { e ->
if (e is ResolvableApiException) {
// Location settings are not satisfied, but this can be fixed
// by showing the user a dialog.
try {
// Show the dialog by calling startResolutionForResult(),
// and check the result in onActivityResult().
e.startResolutionForResult(
this,
requestLocation
)
} catch (sendEx: SendIntentException) {
// Ignore the error.
}
}
}
}
}