Im making a running app and using Sdk mapbox. The requirement is when user moves, I need to draw a layer from the beginning location to the latest one,
I tried this way but it didn’t work
class LocationChangeListeningActivityLocationCallback internal constructor(activity: MapBoxActivity) : LocationEngineCallback<LocationEngineResult> {
private val activityWeakReference: WeakReference<MapBoxActivity> = WeakReference(activity)
override fun onSuccess(result: LocationEngineResult) {
val activity = activityWeakReference.get()
if (activity != null) {
val location = result.lastLocation ?: return
if (result.lastLocation != null) {
activity.mapBoxMap.locationComponent.forceLocationUpdate(result.lastLocation)
activity.listPoint.add(Point.fromLngLat(location.latitude, location.longitude))
activity.trackingLine()
}
}
}
override fun onFailure(exception: Exception) {
val activity = activityWeakReference.get()
if (activity != null) {
Toast.makeText(activity, exception.localizedMessage, Toast.LENGTH_SHORT).show();
}
}
}
var listPoint: ArrayList<Point> = ArrayList()
private var geoJson: GeoJsonSource = GeoJsonSource(LINE_SOURCE_ID)
fun trackingLine() {
mapBoxMap.getStyle {
geoJson.setGeoJson((Feature.fromGeometry(LineString.fromLngLats(listPoint))))
LogCat.d("Set new location in geoJSON")
}
}
private fun initDotLinePath(loadedMapStyle: Style) {
loadedMapStyle.addSource(geoJson)
loadedMapStyle.addLayerBelow(LineLayer(LAYER_ID_3, LINE_SOURCE_ID).withProperties(
PropertyFactory.lineColor(Color.parseColor("#F13C6E")),
PropertyFactory.lineCap(Property.LINE_CAP_ROUND),
PropertyFactory.lineJoin(Property.LINE_JOIN_ROUND),
PropertyFactory.lineWidth(4f)), "road-label")
}
Please show me my mistakes. Thanks a lot
activity.listPoint.add(Point.fromLngLat(location.latitude, location.longitude))
Your coordinates are in the wrong order.
Related
I am trying to implement a way to get the location once and then return it in Kotlin. This is implemented in an Android widget to fetch a location that is used to fetch more data that is then displayed in a widget.
Currently my implementation is as following:
suspend fun getLocation(
context: Context,
fusedLocationClient: FusedLocationProviderClient
): LocationInterface? {
if (ContextCompat.checkSelfPermission(
context,
android.Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
context,
android.Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return null
}
val def = CompletableDeferred<LocationInterface?>()
fusedLocationClient.getCurrentLocation(Priority.PRIORITY_BALANCED_POWER_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(p0: OnTokenCanceledListener) = CancellationTokenSource().token
override fun isCancellationRequested() = false
})
.addOnSuccessListener { location: Location? ->
if (location == null) {
def.complete(null)
}
var name = ""
try {
runBlocking {
val geocoder = Geocoder(context, Locale.getDefault())
val addresses =
geocoder.getFromLocation(location!!.latitude, location!!.longitude, 1)
}
} catch (e: IOException) {
def.complete(null)
}
if (name.isNotEmpty()) {
def.complete(
LocationInterface(
name,
location!!.latitude,
location.longitude,
)
)
} else {
def.complete(null)
}
}.addOnFailureListener {
def.complete(null)
}
return def.await()
}
It works in my emulator, but I get consistently get errors from real devices with java.lang.NullPointerException referring to getLocation$3$1.invokeSuspend
Does anyone know what I may be doing wrong or have a better solution to simply fetch the location and reverse geocode it and return it from the function that makes it?
Thanks in advance!
#HiltViewModel
class HistoryViewModel #Inject constructor(private val firebaseRepository: FirebaseRepository) :
ViewModel() {
private val translateList: MutableList<Translate> = mutableListOf()
private val _translateListState: MutableStateFlow<List<Translate>> =
MutableStateFlow(translateList)
val translateListState = _translateListState.asStateFlow()
init {
listenToSnapshotData()
}
private suspend fun addItemToList(translate: Translate) {
Log.d("customTag", "item added adapter $translate")
translateList.add(translate)
_translateListState.emit(translateList)
}
private suspend fun removeItemFromList(translate: Translate) {
Log.d("customTag", "item removed adapter $translate")
val indexOfItem = translateList.indexOfFirst {
it.id == translate.id
}
if (indexOfItem != -1) {
translateList.removeAt(indexOfItem)
_translateListState.emit(translateList)
}
}
private suspend fun updateItemFromList(translate: Translate) {
Log.d("customTag", "item modified adapter $translate")
val indexOfItem = translateList.indexOfFirst {
it.id == translate.id
}
if (indexOfItem != -1) {
translateList[indexOfItem] = translate
_translateListState.emit(translateList)
}
}
private fun listenToSnapshotData() {
viewModelScope.launch {
firebaseRepository.translateListSnapshotListener().collect { querySnapshot ->
querySnapshot?.let {
for (document in it.documentChanges) {
val translateData = document.document.toObject(Translate::class.java)
when (document.type) {
DocumentChange.Type.ADDED -> {
addItemToList(translate = translateData)
}
DocumentChange.Type.MODIFIED
-> {
updateItemFromList(translate = translateData)
}
DocumentChange.Type.REMOVED
-> {
removeItemFromList(translate = translateData)
}
}
}
}
}
}
}
}
Here data comes properly in querySnapshot in listenToSnapshotData function. And post that it properly calls corresponding function to update the list.
But after this line _translateListState.emit(translateList) flow doesn't go to corresponding collectLatest
private fun observeSnapShotResponse() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
historyViewModel.translateListState.collectLatest {
Log.d("customTag", "calling submitList from fragment")
translateListAdapter.submitList(it)
}
}
}
}
calling submitList from fragment is called once at the start, but as & when data is modified in list viewmodel, callback doesn't come to collectLatest
This is from StateFlow documentation:
Values in state flow are conflated using Any.equals comparison in a similar way to distinctUntilChanged operator. It is used to conflate incoming updates to value in MutableStateFlow and to suppress emission of the values to collectors when new value is equal to the previously emitted one.
You are trying to emit the same instance of List all the time, which has no effect because of what is written in the docs. You will have to create new instance of the list every time.
I want to implement system with live updates (similar to onSnapshotListener). I heard that this can be done with Kotlin Flow.
Thats my function from repository.
suspend fun getList(groupId: String): Flow<List<Product>> = flow {
val myList = mutableListOf<Product>()
db.collection("group")
.document(groupId)
.collection("Objects")
.addSnapshotListener { querySnapshot: QuerySnapshot?,
e: FirebaseFirestoreException? ->
if (e != null) {}
querySnapshot?.forEach {
val singleProduct = it.toObject(Product::class.java)
singleProduct.productId = it.id
myList.add(singleProduct)
}
}
emit(myList)
}
And my ViewModel
class ListViewModel: ViewModel() {
private val repository = FirebaseRepository()
private var _products = MutableLiveData<List<Product>>()
val products: LiveData<List<Product>> get() = _produkty
init {
viewModelScope.launch(Dispatchers.Main){
repository.getList("xGRWy21hwQ7yuBGIJtnA")
.collect { items ->
_products.value = items
}
}
}
What do I need to change to make it work? I know data is loaded asynchronously and it doesn't currently work (the list I emit is empty).
You can use this extension function that I use in my projects:
fun Query.snapshotFlow(): Flow<QuerySnapshot> = callbackFlow {
val listenerRegistration = addSnapshotListener { value, error ->
if (error != null) {
close()
return#addSnapshotListener
}
if (value != null)
trySend(value)
}
awaitClose {
listenerRegistration.remove()
}
}
It uses the callbackFlow builder to create a new flow instance.
Usage:
fun getList(groupId: String): Flow<List<Product>> {
return db.collection("group")
.document(groupId)
.collection("Objects")
.snapshotFlow()
.map { querySnapshot ->
querySnapshot.documents.map { it.toObject<Product>() }
}
}
Note that you don't need to mark getList as suspend.
Starting in firestore-ktx:24.3.0, you can use the Query.snapshots() Kotlin flow to get realtime updates:
suspend fun getList(groupId: String): Flow<List<Product>> {
return db.collection("group")
.document(groupId)
.collection("Objects")
.snapshots().map { querySnapshot -> querySnapshot.toObjects()}
}
As of 2 days ago, firestore has this functionality provided out of the box: https://github.com/firebase/firebase-android-sdk/pull/1252/
I am struggling with firebase to run one query to take the truckDocumentId and after that to run another query to take the routesByDateDocumentIdand at the end I am using both document ids to run the function "sendGpsPosition", my problem is that the first query finds truckDocumentId but sometimes the second query does not execute and that is why the applications stops. The code below is for Kotlin.
If I am on Debug then most of the time works.. if I switch off the debug it almost shows the error below =>
And because the query does not execute I got this error: java.lang.IllegalArgumentException: Invalid document reference. Document references must have an even number of segments, but trucks has 1
suspend fun getTruckId() {
val trucksReference = firestore.collection("trucks").whereEqualTo("dispatcher", "Miro")
.whereEqualTo("name", "PEUGEOT").get().await()
val document = trucksReference.documents[0]
if (document != null) {
truckDocumentId = document.id
}
}
suspend fun getRouteReferenceId() {
val routesByDate = firestore.collection("trucks")
.document(truckDocumentId)
.collection("routes_by_date").get().await()
val documentRoute = routesByDate.documents[0]
if (documentRoute != null) {
routesByDateDocumentId = documentRoute.id
}
}
fun sendGpsPosition(lat: Double, long: Double, imageRef: String? = null) {
runBlocking { getTruckId() } // if I get this DocumentID
runBlocking { getRouteReferenceId() } // this here maybe will be not found or maybe will be found.. the async is not done correct not sure how to do it.
firestore
.collection("trucks")
.document(truckDocumentId)
.collection("routes_by_date")
.document(routesByDateDocumentId)
.collection("live_route")
.add(LatLong(Timestamp.now(), lat, long))
}
**I solved it this way.**
private suspend fun getTruckId() {
val trucksReference = firestore.collection("trucks")
.whereEqualTo("dispatcher", "Miro")
.whereEqualTo("name", "VW")
.get()
.await()
val document = trucksReference.documents[0]
if (document != null) {
truckDocumentId = document.id
}
}
private suspend fun getRouteReferenceId() {
val currentTime = Timestamp.now()
val routesByDate = firestore.collection("trucks")
.document(truckDocumentId)
.collection("routes_by_date")
.get()
.await() // here will be better to look for data by delivery_day
val documentRoute = routesByDate.documents[0]
if (documentRoute != null) {
routesByDateDocumentId = documentRoute.documents[0].id
}
}
private fun addGpsDataInDatabase(lat: Double, long: Double, imageRef: String? = null) {
firestore
.collection("trucks")
.document(truckDocumentId)
.collection("routes_by_date")
.document(routesByDateDocumentId)
.collection("planned_route") //planned_route or live_route depends if we want to show current state of a truck of make a route plan
.add(LatLong(Timestamp.now(), lat, long))
}
fun sendGpsPosition(lat: Double, long: Double, imageRef: String? = null) {
GlobalScope.launch {
val truckDocId = async { getTruckId() }
truckDocId.await()
val routeDocId = async { getRouteReferenceId() }
routeDocId.await()
addGpsDataInDatabase(lat, long, imageRef)
}
}
I have the following code for music recognition. I am using intent service to do all the music recognition in the service. I have done all the basic steps like adding all the permissions required and adding the ACRCloud android SDK in the project.
class SongIdentifyService(discoverPresenter : DiscoverPresenter? = null) : IACRCloudListener , IntentService("SongIdentifyService") {
private val callback : SongIdentificationCallback? = discoverPresenter
private val mClient : ACRCloudClient by lazy { ACRCloudClient() }
private val mConfig : ACRCloudConfig by lazy { ACRCloudConfig() }
private var initState : Boolean = false
private var mProcessing : Boolean = false
override fun onHandleIntent(intent: Intent?) {
Log.d("SongIdentifyService", "onHandeIntent called" )
setUpConfig()
addConfigToClient()
if (callback != null) {
startIdentification(callback)
}
}
public fun setUpConfig(){
Log.d("SongIdentifyService", "setupConfig called")
this.mConfig.acrcloudListener = this#SongIdentifyService
this.mConfig.host = "some-host"
this.mConfig.accessKey = "some-accesskey"
this.mConfig.accessSecret = "some-secret"
this.mConfig.protocol = ACRCloudConfig.ACRCloudNetworkProtocol.PROTOCOL_HTTP // PROTOCOL_HTTPS
this.mConfig.reqMode = ACRCloudConfig.ACRCloudRecMode.REC_MODE_REMOTE
}
// Called to start identifying/discovering the song that is currently playing
fun startIdentification(callback: SongIdentificationCallback)
{
Log.d("SongIdentifyService", "startIdentification called")
if(!initState)
{
Log.d("AcrCloudImplementation", "init error")
}
if(!mProcessing) {
mProcessing = true
if (!mClient.startRecognize()) {
mProcessing = false
Log.d("AcrCloudImplementation" , "start error")
}
}
}
// Called to stop identifying/discovering song
fun stopIdentification()
{
Log.d("SongIdentifyService", "stopIdentification called")
if(mProcessing)
{
mClient.stopRecordToRecognize()
}
mProcessing = false
}
fun cancelListeningToIdentifySong()
{
if(mProcessing)
{
mProcessing = false
mClient.cancel()
}
}
fun addConfigToClient(){
Log.d("SongIdentifyService", "addConfigToClient called")
this.initState = this.mClient.initWithConfig(this.mConfig)
if(this.initState)
{
this.mClient.startPreRecord(3000)
}
}
override fun onResult(result: String?) {
Log.d("SongIdentifyService", "onResult called")
Log.d("SongIdentifyService",result)
mClient.cancel()
mProcessing = false
val result = Gson().fromJson(result, SongIdentificationResult :: class.java)
if(result.status.code == 3000)
{
callback!!.onOfflineError()
}
else if(result.status.code == 1001)
{
callback!!.onSongNotFound()
}
else if(result.status.code == 0 )
{
callback!!.onSongFound(MusicDataMapper().convertFromDataModel(result))
//callback!!.onSongFound(Song("", "", ""))
}
else
{
callback!!.onGenericError()
}
}
override fun onVolumeChanged(p0: Double) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
interface SongIdentificationCallback {
// Called when the user is offline and music identification failed
fun onOfflineError()
// Called when a generic error occurs and music identification failed
fun onGenericError()
// Called when music identification completed but couldn't identify the song
fun onSongNotFound()
// Called when identification completed and a matching song was found
fun onSongFound(song: Song)
}
}
Now when I am starting the service I am getting the following error:
I checked the implementation of the ACRCloudClient and its extends android Activity. Also ACRCloudClient uses shared preferences(that's why I am getting a null pointer exception).
Since keeping a reference to an activity in a service is not a good Idea its best to Implement the above code in the activity. All the implementation of recognizing is being done in a separate thread anyway in the ACRCloudClient class so there is no point of creating another service for that.