I'm trying to load this webmap (https://oebb.maps.arcgis.com/home/item.html?id=f89eab37e55540f7b2e25a88cd0a07d5), but most of the layers don't show up.
It works on the web, but fails on Android (with the newest sdk, 100.2.1).
class MainActivity : AppCompatActivity() {
lateinit var mMapView: MapView
private lateinit var map: ArcGISMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// inflate MapView from layout
mMapView = findViewById(R.id.mapview)
val leftPoint = Point(16.374120968073157, 48.186396114084104, SpatialReferences.getWgs84())
val rightPoint = Point(16.38101960965946, 48.18357774813336, SpatialReferences.getWgs84())
val initialExtent = Envelope(leftPoint, rightPoint)
//construct a map from the portal item
map = ArcGISMap("https://oebb.maps.arcgis.com/home/item.html?id=f89eab37e55540f7b2e25a88cd0a07d5")
// set starting envelope for the ArcGISMap
map.initialViewpoint = Viewpoint(initialExtent)
// Pass a WebMap to the MapView constructor overload to display it.
mapview.map = map
}
override fun onPause() {
super.onPause()
mMapView.pause()
}
override fun onResume() {
super.onResume()
mMapView.resume()
}
override fun onDestroy() {
super.onDestroy()
mMapView.dispose()
}
}
9 of the 10 layers have an exception (map.operationalLayer[0].loadError: ArcGISRuntimeException: Invalid JSON.
Your code appears to be fine, the issue is probably inside the 100.2.1 Android SDK loading the web map. This issue should be fixed in version 100.3.0 which is due out in a few weeks. I ran your code on an internal build and your layers and symbols rendered similar to the web version.
Related
firstly, the code:
class MapFragment: BaseFragment<MapFragmentBinding>(R.layout.map_fragment) {
var mapView: MapView? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mapView = binding.mapView
MapsInitializer.initialize(requireContext(),
MapsInitializer.Renderer.LATEST
) {}
// seems a bit out of place, but due to the binding variable, from out baseFragment class, it has to be done here
mapView?.onCreate(savedInstanceState)
mapView?.getMapAsync { gMap ->
gMap.setOnMapLoadedCallback {
val randomLocation = LatLng(
Random.nextDouble(-170.0, 170.0),
Random.nextDouble(-170.0, 170.0)
)
gMap.addMarker(MarkerOptions().position(randomLocation).title(randomLocation.toString()))
gMap.animateCamera(CameraUpdateFactory.newLatLngZoom(randomLocation, 10.0f))
}
}
}
override fun onStart() {
super.onStart()
mapView?.onStart()
}
override fun onPause() {
super.onPause()
mapView?.onPause()
}
override fun onResume() {
super.onResume()
mapView?.onResume()
}
override fun onStop() {
super.onStop()
mapView?.onStop()
}
override fun onLowMemory() {
super.onLowMemory()
mapView?.onLowMemory()
}
override fun onDestroy() {
super.onDestroy()
mapView?.onDestroy()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView?.onSaveInstanceState(outState)
}
}
this is a mapFragment that is included into another fragment. It is reloaded fairly often (to be precise, the app has a recycler view filled with items, whenever one is pressed this fragment is visible).
I am not sure why but sometimes (whenever it is opened and closed 3-7 times) it animates the camera to random location, and does not display the marker at all. I noticed that it happens mostly whenever the "random location" is at Arctic Ocean or Antarctica (believe me I don't know whether that is related at all, I am as confused as you are).
Any hints/ideas why that might be happening?
Your range of a random latitude is invalid -170.0, 170.0. The map camera implementation will likely peg the out-of-range value to -90 or 90 and the map will refuse to place the marker.
A maximum range for random position could use :
val randomLocation = LatLng(
Random.nextDouble(-90.0, 90.0), // kotlin Random produces [-90,90)
Random.nextDouble(-180.0, 180.0)
The maps api for latitude permits:
[-90, 90] // inclusive at both ends
and for longitude permits:
[-180, 180) // inclusive on the negative and exclusive on the positive
I am completely new to Kotlin, Coroutines and API calls, and I am trying to make an app based on this API.
My intention is to display the information of a game in my MainActivity, so I need ot fill some TextView for that purpose.
My API call and response system works perfectly well: the responses are OK and there are no errors, but the call is made using Kotlin's coroutines which won't let me update my UI after getting the response.
For the sake of simplicity, I am only attaching my MainActivity code, which is the source of the problem.
class MainActivity : AppCompatActivity() {
private lateinit var b: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
b = ActivityMainBinding.inflate(layoutInflater)
setContentView(R.layout.activity_main)
listAllGames()
}
private fun listAllGames() {
CoroutineScope(Dispatchers.IO).launch {
val call = getRetrofit().create(APIService::class.java).listAllGames("")
val games = call.body()
runOnUiThread {
if (call.isSuccessful) {
b.gameTitle.text = games?.get(0)?.title ?: "Dummy"
b.gameDesc.text = games?.get(0)?.short_description ?: "Dummy"
b.gameGenre.text = games?.get(0)?.genre ?: "Dummy"
}
else {
Toast.makeText(applicationContext, "ERROR", Toast.LENGTH_SHORT).show()
Log.d("mydebug", "call unsuccessful")
}
}
}
}
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://www.freetogame.com/api/games/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
Concretely, the listAllGames() method is the problem here: the app will build successfully, if a breakpoint is added inside the if (call.isSuccessful) block, it'll show the data correctly; but when running the app the display will be left blank forever.
Thanks to all in advance!
In order to use view binding you need to pass the inflated view from the binding to the setContentView method. Otherwise you inflate the view with the binding but display the XML layout without the binding.
Check the documentation here:
private lateinit var binding: ResultProfileBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
(Source: Android Developer documentation - " View Binding Part of Android Jetpack.")
Change your onCreate method as followed:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
b = ActivityMainBinding.inflate(layoutInflater)
setContentView(b.root)
listAllGames()
}
The other answer explains your problems with your view, but you also have some issues with the coroutine.
You should use lifecycleScope instead of creating a new scope. lifecycleScope will automatically cancel itself to avoid leaking the Activity if the activity is destroyed, such as during a screen rotation.
You should use Retrofit's call.await() suspend function instead of directly blocking a thread and having to specify a dispatcher. This also lets you leave things on the main dispatcher and directly update UI without having to use runOnUiThread.
private fun listAllGames() {
lifecycleScope.launch {
val call = getRetrofit().create(APIService::class.java).listAllGames("")
try {
val games = call.await()
b.gameTitle.text = games?.get(0)?.title ?: "Dummy"
b.gameDesc.text = games?.get(0)?.short_description ?: "Dummy"
b.gameGenre.text = games?.get(0)?.genre ?: "Dummy"
} catch (e: Exception) {
Toast.makeText(applicationContext, "ERROR", Toast.LENGTH_SHORT).show()
Log.d("mydebug", "call unsuccessful", e)
}
}
}
And actually, you should move API calls like this into a ViewModel so they can keep running during a screen rotation. If you do that, the function in the ViewModel should update a LiveData or SharedFlow instead of UI elements. Then your Activity can observe for changes. If the call is started and the screen is rotated, the API call will keep running and still publish its changes to the new Activity's UI without having to restart.
I'm creating firebase phone-auth application. (I am learning firebase and navigation controller side by side).
I've 1 activity having NavHostFragment in it, I have 2 fragments (1. for getting phone number, 2. for entering and validating OTP)
There is nothing in my activity. I've crated navigation graph etc. perfectly, and everything (related with navigation controller) is working fine.
So after I added Firebase phone-auth in my fragment-1, I released that the activity does nothing other than controlling fragments.(Question is in the end. See que(2))
Also what about
.setActivity(this) // Activity (for callback binding)
We can't use this in fragment.
My questions are:
Alternative of .setActivity(this) in fragments (kotlin)
Is this correct way, or I should implement it in activity by sending values from fragment to activity.
For detailed code:
class IdentityFragment : Fragment() {
private var _binding: FragmentIdentityBinding? = null
private val binding get() = _binding!!
lateinit var auth: FirebaseAuth
lateinit var storedVerificationId:String
lateinit var resendToken: PhoneAuthProvider.ForceResendingToken
private lateinit var callbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
auth=FirebaseAuth.getInstance()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.etPhone.requestFocus()
binding.btnSendotp.setOnClickListener {
sendOTP()
}
}
private fun sendOTP() {
var phone=binding.etPhone.text.toString().trim()
if(!phone.isEmpty()) {
phone = "+91" + phone
sendVerificationCode(phone)
}
}
private fun sendVerificationCode(phone: String) {
val options = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(phone) // Phone number to verify
.setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit
.setActivity()// problem is here
.setCallbacks(callbacks) // OnVerificationStateChangedCallbacks
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
In a Fragment, You can use the getActvity() or requireActivity() method to get the activity instance to which this fragment is attached currently.
setActivity(this.requireActivity())//for kotlin getActivity() available in property access as activity
You can still have the callback in the fragment, where you will get the callback of success or failure.
I am creating an app which will load videos from youtube. The problem I am having is that i can not figure out how to instantiate a YoutubePlayerView inside an android fragment since my app only has one activity and all of the UI stuff is managed through fragments and navigation graph.
The only thing that i could figure out is instantiating YoutubePlayerView inside a YoutubeBaseActivity which will compromise the consistency of navigation since it will be a new activity.
I tried creating a YoutubePlayerFragment but it can't be set as a destination in a navigation graph.
So I will be thankful if anyone can help me figure out how to do this. :)
Thanks in advance.
For anyone having this issue today, this is what I did and it works perfectly for Activity and Fragment.
1. PLACE A FRAMELAYOUT
In your Activity or Fragment XML, place a FrameLayout. Simple enough.
<FrameLayout
android:id="#+id/main_player"
android:layout_width="match_parent"
android:layout_height="0dp"/>
2. INSTANTIATE YoutubePlayerSupportFragment AND ADD IT TO THE FRAME LAYOUT
Type in the next lines and if you get an error regarding the use of android.support.v4.Fragment you're going to have to create a new custom class of YoutubePlayerSupportFragment. I think this error happens because the YoutubePlayerSupportFragment class extends from android.support.v4.Fragment and I'm using androidx, I'm not sure. Still, I'm doing this inside a click listener, but you can do it anywhere else like in onViewCreated() or in onCreateView()
val youtubePlayerSupportFragment = YouTubePlayerSupportFragment.newInstance()
supportFragmentManager.beginTransaction()
.add(R.id.main_player, youtubePlayerSupportFragment).commit()
// these lines of code populates the FrameLayout with a brand new YoutubePlayerSupportFragment
// this code is actually for an Activity. If you want to do it for a fragment, replace the `supportFragmentManager` for `childFragmentManager` and it should work as well. It does for me.
So if these lines of code give you error, you have create a new class in this exact new path com.google.android.youtube.player and inside this directory create a new class. You can call it whatever you want. Just copy and paste this:
package com.google.android.youtube.player //<--- IMPORTANT!!!!
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.youtube.player.internal.ab
import java.util.*
class YouTubePlayerSupportFragmentX : Fragment(), YouTubePlayer.Provider {
private val a = ViewBundle()
private var b: Bundle? = null
private var c: YouTubePlayerView? = null
private var d: String? = null
private var e: YouTubePlayer.OnInitializedListener? = null
override fun initialize(var1: String, var2: YouTubePlayer.OnInitializedListener) {
d = ab.a(var1, "Developer key cannot be null or empty")
e = var2
a()
}
private fun a() {
if (c != null && e != null) {
c?.a(this.activity, this, d, e, b)
b = null
e = null
}
}
override fun onCreate(var1: Bundle?) {
super.onCreate(var1)
b = var1?.getBundle("YouTubePlayerSupportFragment.KEY_PLAYER_VIEW_STATE")
}
override fun onCreateView(var1: LayoutInflater, var2: ViewGroup?, var3: Bundle?): android.view.View? {
c = YouTubePlayerView(Objects.requireNonNull(this.activity), null, 0, a)
a()
return c
}
override fun onStart() {
super.onStart()
c?.a()
}
override fun onResume() {
super.onResume()
c?.b()
}
override fun onPause() {
c?.c()
super.onPause()
}
override fun onSaveInstanceState(var1: Bundle) {
super.onSaveInstanceState(var1)
(if (c != null) c?.e() else b)?.let { var2 ->
var1.putBundle("YouTubePlayerSupportFragment.KEY_PLAYER_VIEW_STATE", var2)
}
}
override fun onStop() {
c?.d()
super.onStop()
}
override fun onDestroyView() {
this.activity?.let { c?.c(it.isFinishing) }
c = null
super.onDestroyView()
}
override fun onDestroy() {
if (c != null) {
val var1 = this.activity
c?.b(var1 == null || var1.isFinishing)
}
super.onDestroy()
}
private inner class ViewBundle : YouTubePlayerView.b {
override fun a(var1: YouTubePlayerView, var2: String, var3: YouTubePlayer.OnInitializedListener) {
e?.let { initialize(var2, it) }
}
override fun a(var1: YouTubePlayerView) {}
}
companion object {
fun newInstance(): YouTubePlayerSupportFragmentX {
return YouTubePlayerSupportFragmentX()
}
}
}
Line 36 is going to stay with a red underline but just dont pay atention to it. Couldn't find a way to solve it but it works fine.
In my case, I'm using my custom class so the lines of code I typed above turn out to be very similar:
val youtubePlayerSupportFragment = YouTubePlayerSupportFragmentX.newInstance() //notice this is my custom class, it ends with an X
supportFragmentManager.beginTransaction()
.add(R.id.main_player, youtubePlayerSupportFragment).commit()
3. INITIALIZE YOUTUBE PLAYER SUPPORT FRAGMENT
Now you should have your YoutubePlayerSupportFragment ready to go.
After being sure this works, you have to initialize the youtubePlayer. You have to do that just by calling youtubePlayerSupportFragment.initialize(apiKey, listener). Inside the parenthesis you have to set your apiKey and a listener. The api key can be generated easily, just google it if you don't have one. And for the listener you can do ctrl + shift + spacebar to open the smart suggestions, and it should pop up first. In my case it looks something like this:
val youtubePlayerSupportFragment = YouTubePlayerSupportFragmentX.newInstance()
supportFragmentManager.beginTransaction()
.add(R.id.main_player, youtubePlayerSupportFragment).commit()
youtubePlayerSupportFragment.initialize(
resources.getString(R.string.API_KEY), //IF YOU HAVE NO API KEY IT WONT WORK. But that's actually explained in the docs. So you can google it easily if you don't have one
object : YouTubePlayer.OnInitializedListener {
override fun onInitializationSuccess(
p0: YouTubePlayer.Provider?,
p1: YouTubePlayer?,
p2: Boolean
) {
p1?.loadVideo("9ET6R_MR1Ag") // string has to be https://www.youtube.com/watch?v=----------->9ET6R_MR1Ag<---------
}
override fun onInitializationFailure(
p0: YouTubePlayer.Provider?,
p1: YouTubeInitializationResult?
) {
layoutUtils.createToast(
applicationContext,
"ERROR INITIATING YOUTUBE"
)
}
})
And that's it! Lot of work, yeah, but it's worth it. It works perfectly even though the api documentation is poorly written and it has some bugs. But to the user looks exactly like youtube so it's going to give your app a very professional look. At least that's the feedback I got when I implemented it.
Important note:
If you want to play a video, and let the user be able to play other videos on cue, like on a click just like youtube, you have to store the youtubePlayer in a variable the moment it is initialized. For example:
var youtubePlayer: YoutubePlayer? = null // this is a global variable of your class
And when the player is successfully initiated, store it like this:
override fun onInitializationSuccess(
p0: YouTubePlayer.Provider?,
p1: YouTubePlayer?,
p2: Boolean
) {
p1?.loadVideo(video)
youtubePlayer = p1 // <------- this line here
}
Then in any part of your code you can just do youtubePlayer.loadVideo(anotherLink) and it will automatically stop the current video and start playing the new one.
Dear StackOverflow Community!
My question might be a rookie one, I feel like I'm missing something very basic. I tried to make an ArcGIS map work under an android ViewPager2 structure. The map diplays nicely but when I navigate away to another fragment in the view pager, then back to the map, the app crashes with the following exception.
com.esri.arcgisruntime.ArcGISRuntimeException: vector:
/home/jenkins/100.7.0/dev_android_java_RTCA_release/runtimecore/c_api/src/mapping/map_view/geo_view.cpp(701) : error : Exception caught in __FUNCTION__
at com.esri.arcgisruntime.internal.jni.CoreGeoView.nativeDraw(Native Method)
at com.esri.arcgisruntime.internal.jni.CoreGeoView.a(SourceFile:346)
at com.esri.arcgisruntime.internal.h.b.o.a(SourceFile:132)
at com.esri.arcgisruntime.mapping.view.MapView.onDrawFrame(SourceFile:156)
at com.esri.arcgisruntime.mapping.view.GeoView$b.onDrawFrame(SourceFile:1363)
at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1573)
at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1272)
This seems to happen every time when the onResume() method of the Fragment containing the MapView is called. In this function (ie. the onResume()) I manually call onResume() on the MapView instance as indicated in this walkthrough:
https://developers.arcgis.com/labs/android/create-a-starter-app/
I extracted the problematic part of the code to a test app, I removed all layers, now it's just an empty basemap in an empty app (under the view pager structure) and the crash persists.
The reason why I think this problem could be connected with the ViewPager2 is because in a previous version of the app, I used a different navigation structure without the view pager and the map was working fine.
The difference between my actual code and the above walkthrough is that it puts the MapView directly under the MainActivity while I put it in a fragment as I'm working with a view pager.
It was not absolutely clear to me if I still have to put the appropriate onPause(), onResume(), onDestroy() calls under the fragment class or under the main activity so I tried both (you can see the former in the code below) and I also tried removing those override functions completely. The exception was the same in each case.
Here is the test app MainActivity class building the view pager.
class MainActivity : AppCompatActivity() {
private lateinit var adapter: ViewPagerFragmentAdapter
private lateinit var viewPager: ViewPager2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
adapter = ViewPagerFragmentAdapter(supportFragmentManager, lifecycle)
viewPager = findViewById(R.id.view_pager)
viewPager.adapter = adapter
}
}
And the MapFragment class.
class MapFragment: Fragment() {
lateinit var mMapView: MapView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_map, container, false)
ArcGISRuntimeEnvironment.setLicense(resources.getString(R.string.arcgis_license_key))
mMapView = view.findViewById(R.id.map_view)
val basemapType = Basemap.Type.IMAGERY_WITH_LABELS
val latitude = 48.0166175
val longitude = 19.0339708
val levelOfDetail = 2
mMapView.map = ArcGISMap(basemapType, latitude, longitude, levelOfDetail)
return view
}
override fun onPause() {
if (mMapView != null) {
mMapView.pause()
}
super.onPause()
}
override fun onResume() {
super.onResume()
if (mMapView != null) {
mMapView.resume()
}
}
override fun onDestroy() {
if (mMapView != null) {
mMapView.dispose()
}
super.onDestroy()
}
}
Could you please give me any indicaton on where I could go wrong?
Thank you very much for any help in advance!
Mark
For anyone bumping into problems like this, always check your dependencies first :)
In my case, the ESRI lib was outdated. When I switched from the outdated dependency:
dependencies {
...
implementation 'com.esri.arcgisruntime:arcgis-android:100.7.0'
...
}
to the latest one:
dependencies {
...
implementation 'com.esri.arcgisruntime:arcgis-android:100.8.0'
...
}
the exception disappeared and the app works as expected.