State flow Android Kotlin - kotlin

I have a god view model for every thing I know this is wrong
but I am just experimenting with Flow
I have these two State flow variables in view model
private val _currentRestroMenu = MutableStateFlow<State<Menu>>(State.loading())
private val _userCart = MutableStateFlow(CustomerCart())
val currentRestroMenu: StateFlow<State<Menu>> = _currentRestroMenu
val userCart: StateFlow<CustomerCart> = _userCart
Below functions get data from server and update above state flow
private fun getRestroMenuFromCloudAndUpdateData(restroId: String) = viewModelScope.launch {
fireStoreRepository.getRestroMenu(restroId).collect { state ->
when (state) {
is State.Success -> {
_currentRestroMenu.value = State.success(state.data)
dataHolderMenuOnSearch = state.data
if (!viewedRestroMenu.contains(state.data)) {
viewedRestroMenu.add(state.data)
}
}
is State.Failed -> {
_currentRestroMenu.value = State.failed(state.message)
}
is State.Loading -> {
_currentRestroMenu.value = State.loading()
}
}
}
}
private fun getCart() = viewModelScope.launch(Dispatchers.IO) {
if (currentCart.cartEmpty) {
fireStoreRepository.getUserCartInfoFromCloud(dataStoreRepository.readFileDataStoreValue.first().savedUserId)
.collect { cartState ->
when (cartState) {
is State.Success -> {
_userCart.update {
it.copy(
cartId = cartState.data.cartId,
cartEmpty = cartState.data.cartEmpty,
cartItem = cartState.data.getCartItem(),
restroId = cartState.data.restroId,
cartTotalAmount = cartState.data.cartTotalAmount,
cartAddressId = cartState.data.cartAddressId,
cartDeliveryTime = cartState.data.cartDeliveryTime,
cartCookingInstructions = cartState.data.cartCookingInstructions,
cartAppliedOfferId = cartState.data.cartAppliedOfferId,
deliveryPartnerTipAmount = cartState.data.deliveryPartnerTipAmount,
cartDeliveryCharge = cartState.data.cartDeliveryCharge,
cartTax = cartState.data.cartTax,
deliveryInstructionId = cartState.data.deliveryInstructionId,
foodHandlingCharge = cartState.data.foodHandlingCharge,
cartNumberOfItems = cartState.data.cartNumberOfItems,
cartRestroName = cartState.data.cartRestroName
)
}
currentCart = cartState.data
}
is State.Failed -> {
if (cartState.message == "Result null") {
Log.d(
ContentValues.TAG,
"getCartFromCloud: No cart details found in cloud creating new cart"
)
_userCart.update {
it.copy(
cartId = dataStoreRepository.readFileDataStoreValue.first().savedUserId,
cartEmpty = true
)
}
currentCart = CustomerCart(
cartId = dataStoreRepository.readFileDataStoreValue.first().savedUserId,
cartEmpty = true
)
}
}
is State.Loading -> {
Log.d(ContentValues.TAG, "getCartFromCloud: Loading")
}
}
}
} else {
_userCart.value = currentCart
Log.d(ContentValues.TAG, "getCart: $currentCart ")
}
}
I am collecting these state flow from different fragments
every thing works fine except one fragment
here is the code
in on create method
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
godCustomerViewModel.currentRestroMenu.collectLatest { menuState ->
Log.d(TAG, "currentRestroMenu ::: mENUSELECT FIRED: ")
when (menuState) {
is State.Success -> {
restroMenu = menuState.data
binding.recyclerView2.hideShimmer()
getCartDetails(restroMenu)
}
is State.Failed -> {
Log.d(TAG, "currentRestroMenu: ")
}
is State.Loading -> {
binding.recyclerView2.showShimmer()
}
}
}
}
}
private fun getCartDetails(restroMenu: Menu) = viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
godCustomerViewModel.userCart.collectLatest {
if (it.restroId == restroMenu.restroId) {
categoryAdapterRestroDetails.setData(
restroMenu.menuCategories,
it.getCartItem()
)
} else {
categoryAdapterRestroDetails.setData(
restroMenu.menuCategories,
ArrayList()
)
}
}
}
}
I am passing the two collected values to adapter (retro menu and item in cart )
when the fragment is loaded for the first time everything works fine
I have add dish to cart function which updates the value of user cart
fun addDishToCart(dish: Dish) = viewModelScope.launch {
Log.d(ContentValues.TAG, "addDishToCart: view model invoked")
if (currentCart.checkIfCartBelongsToThisRestro(dish.dishRestroId)) {
currentCart.addDishToCart(dish).collect {
Log.d(ContentValues.TAG, "addDishToCartcollect: $currentCart")
_userCart.update {
it.copy(
cartEmpty = currentCart.cartEmpty,
cartItem = currentCart.getCartItem(),
restroId = currentCart.restroId,
cartTotalAmount = currentCart.cartTotalAmount,
cartNumberOfItems = currentCart.cartNumberOfItems,
)
}
}
} else {
// restro Conflict
Log.d(ContentValues.TAG, "addDishToCart: $currentCart")
_restroConflict.value = CartConflict(true, currentCart.cartRestroName, dish)
}
Log.d(ContentValues.TAG, "addDishToCart current cart: ${currentCart.getCartItem()}")
Log.d(ContentValues.TAG, "addDishToCart: user Cart : ${_userCart.value.getCartItem()} ")
}
Which also work fine initially
I also have a button to filter menu to veg non veg
fun filterMenuForVeg(value: Boolean, showAll: Boolean) = viewModelScope.launch {
if (!showAll) {
Log.d(ContentValues.TAG, "filterMenuForVeg: Entered veg :$value")
var filteredMenu = Menu()
filteredMenu.restroId = dataHolderMenuOnSearch.restroId
for (menuCategory in dataHolderMenuOnSearch.menuCategories) {
Log.d(ContentValues.TAG, "filterMenuForVeg: $dataHolderMenuOnSearch ")
for (dish in menuCategory.dishes) {
if (dish.dishVeg == value) {
Log.d(ContentValues.TAG, "found dish with veg $value: ")
var categoryAlreadySaved = false
filteredMenu.menuCategories.filter {
categoryAlreadySaved = it.categoryId == menuCategory.categoryId
true
}
if (!categoryAlreadySaved) {
Log.d(ContentValues.TAG, "menu category not found in filtered list ")
val menuCategoryToAdd = MenuCategories()
menuCategoryToAdd.menuCategoryName = menuCategory.menuCategoryName
menuCategoryToAdd.categoryId = menuCategory.categoryId
menuCategoryToAdd.restroId = menuCategory.restroId
menuCategoryToAdd.dishes.add(dish)
filteredMenu.menuCategories.add(menuCategoryToAdd)
} else {
Log.d(ContentValues.TAG, "menu category found in filtered list ")
filteredMenu.menuCategories.find {
if (it.categoryId == menuCategory.categoryId) {
it.restroId = menuCategory.restroId
it.dishes.add(dish)
}
true
}
}
}
}
}
Log.d(ContentValues.TAG, "filterMenuForVeg : $filteredMenu ")
_currentRestroMenu.value = State.success(filteredMenu)
} else {
// set to all data
_currentRestroMenu.value = State.success(dataHolderMenuOnSearch)
}
When I filter dish for veg or non veg then add dish to cart (Which only changes userCart State flow) the place where I am collecting these state flow
get fired twice
so set data to adapter is getting called twice
What Iam doing wrong

Could you collect the items with onEach instead of collectLatest? It would solve your problem probably.

Related

Get Address from current location Kotlin

I get the current location like this:
val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(LocalContext.current)
fun getLastKnownLocation() {
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
if (location != null) {
longitude.value = location.longitude
latitude.value = location.latitude
}
}
}
How I can get the Address from it?
Update
I implemented like this:
fun getLastKnownLocation() {
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
if (location != null) {
longitude.value = location.longitude
latitude.value = location.latitude
val geocoder = Geocoder(app)
if (Build.VERSION.SDK_INT >= 33) {
geocoder.getFromLocation(
location.latitude, location.longitude, 1
) { addresses ->
address.value = addresses.first().toString()
}
}
else {
try {
val addresses = geocoder.getFromLocation(location.latitude, location.longitude, 1
)?.firstOrNull()
address.value = addresses.toString()
} catch (Exp: Exception) {
address.value = "No Address found"
println("$Exp")
}
}
}
}
}
But got this exception: java.io.IOException: eccc: DEADLINE_EXCEEDED: deadline exceeded after 4.907678698s
val geocoder = Geocoder(this, Locale.getDefault())
val addresses = geocoder.getFromLocation(location.latitude, location.longitude, 1)
https://developer.android.com/reference/kotlin/android/location/Geocoder

Google Places OpeningHours.getWeekdayText() is NULL for some locations

I'm using google places to retrieve information for a place such as the business name, address, OpenHours and LatLng.
This works 95% of the time, but for some places I receive the error...
"Attempt to invoke virtual method 'java.util.List com.google.android.libraries.places.api.model.OpeningHours.getWeekdayText()' on a null object reference"
Looking on Google maps, I do see these places have open hours information.
//Add a marker when a POI on map is clicked.
map.setOnPoiClickListener { poi ->
map.clear()
val poiMarker = map.addMarker(
MarkerOptions()
.position(poi.latLng)
.title(poi.name)
)
poiMarker?.showInfoWindow()
placeId = poi.placeId
Timber.i("Place ID: $placeId")
//https://developers.google.com/maps/documentation/places/android-sdk/reference/com/google/android/libraries/places/api/net/PlacesClient#fetchPlace(com.google.android.libraries.places.api.net.FetchPlaceRequest)
val placeFields = listOf(Place.Field.ID, Place.Field.NAME, Place.Field.ADDRESS, Place.Field.OPENING_HOURS, Place.Field.LAT_LNG)
val request = FetchPlaceRequest.newInstance(placeId, placeFields)
if (!Places.isInitialized()) {
Places.initialize(requireContext(), apiKey, Locale.US);
}
val placesClient = Places.createClient(requireContext())
placesClient.fetchPlace(request)
.addOnSuccessListener { response: FetchPlaceResponse ->
val place = response.place
setLiveDataPlace(place)
}.addOnFailureListener { exception: Exception ->
if (exception is ApiException) {
Timber.i( "Place not found: ${exception.message}")
}
}
binding.buttonSave.visibility = View.VISIBLE
}
}
fun setLiveDataPlace(place: Place){
placeId = place.id as String
placeName = place.name as String
placeAddress = place.address as String
try {
placeOpenMonday = place.openingHours.weekdayText[0]
placeOpenTuesday = place.openingHours.weekdayText[1]
placeOpenWednesday = place.openingHours.weekdayText[2]
placeOpenThursday = place.openingHours.weekdayText[3]
placeOpenFriday = place.openingHours.weekdayText[4]
placeOpenSaturday = place.openingHours.weekdayText[5]
placeOpenSunday = place.openingHours.weekdayText[6]
} catch(e : Exception) {
Timber.i("Open hours exception: ${e.message}")
}
placeLat = place.latLng.latitude.toString()
placeLng = place.latLng.longitude.toString()
Timber.i("Place: $place")
}

I tried to run this code and every time I run it shows this result

private fun moveMarkerAnimation(key: String, newData: AnimationModel, marker: Marker?, from: String, to: String) {
if (!newData.isRun)
{
compositeDisposable.add(
iGoogleAPI.getDirections(
"driving",
"less_driving",
from, to,
getString(R.string.google_api_key1)
)!!.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { returnResult ->
Log.d("API_RETURN",returnResult,)
try {
val jsonObject = JSONObject(returnResult)
val jsonArray = jsonObject.getJSONArray("routes")
for ( i in 0 until jsonArray.length())
{
val route = jsonArray.getJSONObject(i)
val poly = route.getJSONObject("overview_polyLine")
val polyLine = poly.getString("points")
polylineList = Common.decodePoly(polyLine) as java.util.ArrayList<LatLng?>
}
handler = Handler()
index = -1
next = 1
val runnable = object :Runnable{
override fun run() {
if (polylineList!!.size > 1)
{
if (index< polylineList!!.size)
{
index ++
next = index+1
start = polylineList!![index] !!
end = polylineList!![next]!!
}
val valueAnimator = ValueAnimator.ofInt(0,1)
valueAnimator.duration = 3000
valueAnimator.interpolator = LinearInterpolator()
valueAnimator.addUpdateListener { value ->
v = value.animatedFraction
lat = v*end !!.latitude + (1-v) * start!!.latitude
lng = v*end !!.longitude+ (1-v) * start!!.longitude
val newPos = LatLng(lat,lng)
marker!!.position = newPos
marker!!.setAnchor(0.5f,0.5f)
marker!!.rotation = Common.getBearing(start!!,newPos)
}
valueAnimator.start()
if (index < polylineList!!.size-2)
{
handler!!.postDelayed(this,1500)
}else if (index < polylineList!!.size-1)
{
newData.isRun = false
Common.driversSubscrib.put(key,newData)
}
}
}
}
handler!!.postDelayed(runnable,1500)
}
catch (e:java.lang.Exception){
Snackbar.make(requireView(),e.message!!,Snackbar.LENGTH_LONG).show()
}
When the site changes from from firebase this result appears
022-04-26 13:19:30.912 23482-23482/com.example.bustrackerriderapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.bustrackerriderapp, PID: 23482
io.reactivex.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading
what should I do to fix this problem

Cannot format given Object as a Number in Kotlin

An error occurred while using the ConverPrice function as follows for information about the price.
The price of the item in the recycler view adapter onBindViewHolder.
As a result of debugging, the error occurs in the following code.
priceText =
"${dec.format(priceMin)} ~ ${dec.format(priceMax)}"
Please check my code and answer.
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is DataViewHolder -> {
val item = dataList[position]
item.price.let {
holder.price.text = ConvertPrice(item, holder.price)
}
}
}
}
fun ConvertPrice(productDetail: ProductDetail?, tv: TextView? = null, setPrice: Boolean = false): String {
val disableColor = Color.parseColor("#aaaaaa")
val enableColor = Color.parseColor("#3692ff")
tv?.setTextColor(disableColor)
if (ProductDetail != null) {
val priceMin = productDetail.priceMin
val priceMax = productDetail.priceMax
var priceText = ""
val dec = DecimalFormat("##,###")
productDetail.enabledRetail?.let {
if (productDetail.enabledRetail == true) {
if (setPrice) {
priceText = if (priceMin == null || priceMax == null) {
"No pricing information"
} else {
"${dec.format(priceMin)} ~ ${dec.format(priceMax)}"
}
tv?.setTextColor(disableColor)
}
else {
priceText = dec.format(wineDetail.price).toString()
tv?.setTextColor(enableColor)
}
return priceText
} else if (productDetail.cntRating!! > 0) {
if ((priceMin == null && priceMax == null) || (priceMin == 0 && priceMax == 0)) {
priceText = "No pricing information"
} else {
priceText =
"${dec.format(priceMin)} ~ ${dec.format(priceMax)}"
tv?.setTextColor(disableColor)
}
return priceText
}
}
}
return "No pricing information"
}
DecimalFormat.format() only works fine with long or double. You should convert "priceMin" and "priceMax" to Long.
val priceMin = productDetail.priceMin.toLong()
val priceMax = productDetail.priceMax.toLong()
I recommend to use NumberFormat instead of DecimalFormat because it is locale-sensitive
val decFormat = NumberFormat.getInstance() // or getCurrencyInstance()
decFormat.maximumFractionDigits = 3
decFormat.format(priceMin)
decFormat.format(priceMax)

How to move the snap position from center to left of RecycleView using SnapHelper?

I have an RecycleView that contains ImageViews and my question is how can i move the snap to be on the left side of the RecycleView instead of the center?
When i move the ImageViews they get snapped in the center and I can move them to the left or right inside that "snap window" by overriding the CalculateDistanceToFinalSnap method. I think I would now need to move that "snap window" to the left side of the RecycleView but I don't know how, or maybe there is another way, please help.
Here is a image of my problem, maybe it will help you to understand more clearly:
image
#Jessie Zhang -MSFT's solution works for me. The code was a little oddly formatted and I had some difficulty bringing it over. Here is the same solution (for a horizontal snap only) in Kotlin.
class StartSnapHelper: LinearSnapHelper() {
override fun calculateDistanceToFinalSnap(layoutManager: RecyclerView.LayoutManager, targetView: View): IntArray? {
return if (layoutManager.canScrollHorizontally()) {
val outer = mutableListOf<Int>()
outer.add(distanceToStart(targetView, getHorizontalHelper(layoutManager)))
outer.add(0)
outer.toIntArray()
} else {
super.calculateDistanceToFinalSnap(layoutManager, targetView)
}
}
override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? {
return if (layoutManager is LinearLayoutManager) {
if (layoutManager.canScrollHorizontally()) {
getStartView(layoutManager, getHorizontalHelper(layoutManager))
} else {
super.findSnapView(layoutManager)
}
} else {
super.findSnapView(layoutManager)
}
}
private fun distanceToStart(targetView: View, helper: OrientationHelper): Int {
return helper.getDecoratedStart(targetView) - helper.startAfterPadding
}
private fun getStartView(layoutManager: RecyclerView.LayoutManager, orientationHelper: OrientationHelper): View? {
val firstChild = (layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
val isLastItem = (layoutManager.findLastCompletelyVisibleItemPosition() == layoutManager.itemCount - 1)
if (firstChild == RecyclerView.NO_POSITION || isLastItem) {
return null
}
val child = layoutManager.findViewByPosition(firstChild)
return if (orientationHelper.getDecoratedEnd(child) >= orientationHelper.getDecoratedMeasurement(child) / 2
&& orientationHelper.getDecoratedEnd(child) > 0) {
child;
} else {
if (layoutManager.findFirstCompletelyVisibleItemPosition() == layoutManager.itemCount -1) {
null
} else {
layoutManager.findViewByPosition(firstChild + 1)
}
}
}
private fun getHorizontalHelper(layoutManager: RecyclerView.LayoutManager): OrientationHelper {
return OrientationHelper.createHorizontalHelper(layoutManager)
}
}
I have achieved this function ,we juse need to create a class and extent class LinearSnapHelper and override method CalculateDistanceToFinalSnap and FindSnapView. You can check out the full demo here .
The main code is as follows:
public class StartSnapHelper: LinearSnapHelper
{
private OrientationHelper mVerticalHelper, mHorizontalHelper;
public StartSnapHelper()
{
}
public override void AttachToRecyclerView(RecyclerView recyclerView)
{
base.AttachToRecyclerView(recyclerView);
}
public override int[] CalculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, View targetView)
{
//return base.CalculateDistanceToFinalSnap(layoutManager, targetView);
int[] outer = new int[2];
if (layoutManager.CanScrollHorizontally())
{
outer[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager));
} else {
outer[0] = 0;
}
if (layoutManager.CanScrollVertically()) {
outer[1] = distanceToStart(targetView, getVerticalHelper(layoutManager));
} else {
outer[1] = 0;
}
return outer;
}
private int distanceToStart(View targetView, OrientationHelper helper)
{
return helper.GetDecoratedStart(targetView) - helper.StartAfterPadding;
}
public override View FindSnapView(RecyclerView.LayoutManager layoutManager)
{
if (layoutManager is LinearLayoutManager) {
if (layoutManager.CanScrollHorizontally())
{
return getStartView(layoutManager, getHorizontalHelper(layoutManager));
}
else
{
return getStartView(layoutManager, getVerticalHelper(layoutManager));
}
}
return base.FindSnapView(layoutManager);
}
private View getStartView(RecyclerView.LayoutManager layoutManager,
OrientationHelper helper)
{
if (layoutManager is LinearLayoutManager) {
int firstChild = ((LinearLayoutManager)layoutManager).FindFirstVisibleItemPosition();
bool isLastItem = ((LinearLayoutManager)layoutManager)
.FindLastCompletelyVisibleItemPosition()
== layoutManager.ItemCount - 1;
if (firstChild == RecyclerView.NoPosition || isLastItem)
{
return null;
}
View child = layoutManager.FindViewByPosition(firstChild);
if (helper.GetDecoratedEnd(child) >= helper.GetDecoratedMeasurement(child) / 2
&& helper.GetDecoratedEnd(child) > 0)
{
return child;
}
else
{
if (((LinearLayoutManager)layoutManager).FindLastCompletelyVisibleItemPosition()
== layoutManager.ItemCount - 1)
{
return null;
}
else
{
return layoutManager.FindViewByPosition(firstChild + 1);
}
}
}
return base.FindSnapView(layoutManager);
}
private OrientationHelper getVerticalHelper(RecyclerView.LayoutManager layoutManager)
{
if (mVerticalHelper == null)
{
mVerticalHelper = OrientationHelper.CreateVerticalHelper(layoutManager);
}
return mVerticalHelper;
}
private OrientationHelper getHorizontalHelper(RecyclerView.LayoutManager layoutManager)
{
if (mHorizontalHelper == null)
{
mHorizontalHelper = OrientationHelper.CreateHorizontalHelper(layoutManager);
}
return mHorizontalHelper;
}
}
And use like this:
SnapHelper snapHelperStart = new StartSnapHelper();
snapHelperStart.AttachToRecyclerView(recyclerView);