The video calling feature was working perfectly with webview until almost 3 months ago. Recently it's not supporting camera and audio functionality, even browser and application have both of these permissions. I would like to know if it is because of any recent update with webview or Chromium or with anything else, as it was working properly before.
Below is the method used to load the URL:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
getBindingModel().webView.settings.javaScriptEnabled = true
getBindingModel().webView.settings.mediaPlaybackRequiresUserGesture = false
getBindingModel().webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
view.loadUrl(url)
return false
}
}
getBindingModel().webView.webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView?, newProgress: Int) {
getBindingModel().loadingText.text = "$newProgress%"
getBindingModel().loadingText.visibility =
if (newProgress > 99) View.GONE else View.VISIBLE
getBindingModel().progress.visibility =
if (newProgress > 99) View.GONE else View.VISIBLE
}
override fun onPermissionRequest(request: PermissionRequest?) {
runOnUiThread { request?.grant(request.resources) }
}
}
}
Related
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed last month.
An error occurs on line 41 when initializing the SongListFragment fragment class.
Here is a snippet of the class:
class SongListFragment : Fragment() {
private var _binding: FragmentSongsListBinding? = null
private val binding get() = _binding!! //error
private var player: ExoPlayer? = null
private lateinit var inputManager: InputMethodManager
private lateinit var sharedPreferences: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
private val songListAdapter by lazy {
SongListAdapter(requireActivity()) { show ->
showMultipleSelectActionBar(
show
)
}
}
private val viewModel: SongListViewModel by activityViewModels()
#SuppressLint("RtlHardcoded")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
player = (context as MainActivity).exoPlayer
inputManager =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
editor = sharedPreferences.edit()
enterTransition = Slide(Gravity.END)
exitTransition = Fade()
returnTransition = Fade()
}
#RequiresApi(Build.VERSION_CODES.Q)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
requireActivity().window.statusBarColor =
ContextCompat.getColor(requireContext(), R.color.main_background_color)
if (_binding == null) {
_binding = FragmentSongsListBinding.inflate(inflater, container, false)
}
postponeEnterTransition()
binding.root.doOnPreDraw { startPostponedEnterTransition() }
viewModel.getSongList(context)
initObserver()
setDataToView()
searchMusic()
multipleSelectButtonImplementation()
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (sharedPreferences.getInt(Constants.AD_COUNTER, 0) == 3) {
(activity as MainActivity).showAd(requireContext())
editor.putInt(Constants.AD_COUNTER, 0)
editor.commit()
} else {
editor.putInt(Constants.AD_COUNTER, (sharedPreferences.getInt(Constants.AD_COUNTER, 0) + 1))
editor.commit()
}
}
stack trace:
Fatal Exception: java.lang.NullPointerException
example.androidvolumelouder.presentation.ui.fragments.songlistfragment.SongListFragment.getBinding (SongListFragment.kt:41)
example.androidvolumelouder.presentation.ui.fragments.songlistfragment.SongListFragment.access$getBinding (SongListFragment.kt:38)
example.androidvolumelouder.presentation.ui.fragments.songlistfragment.SongListFragment$setSongCount$1.invokeSuspend (SongListFragment.kt:225)
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33)
kotlinx.coroutines.DispatchedTaskKt.resume (DispatchedTask.kt:234)
kotlinx.coroutines.DispatchedTaskKt.dispatch (DispatchedTask.kt:166)
kotlinx.coroutines.CancellableContinuationImpl.dispatchResume (CancellableContinuationImpl.kt:397)
kotlinx.coroutines.CancellableContinuationImpl.resumeImpl (CancellableContinuationImpl.kt:431)
kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default (CancellableContinuationImpl.kt:420)
kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched (CancellableContinuationImpl.kt:518)
kotlinx.coroutines.android.HandlerContext$scheduleResumeAfterDelay$$inlined$Runnable$1.run (Runnable.kt:19)
This happens on the first run after installation. And what's more, everything is fine on physical devices with Android 10 on all others and on emulators as well.
What is the error, please help?
The crash is not in the above lines.. However, you are accessing binding after you navigate the screen.
You need to safe call isAdded before using binding as follow: e.g.
if(isAdded){
binding.button.setOnClickListener{ ... }
}
Try to move your UI elements bind logic to onViewCreated
See Fragment | Android Developers
Called immediately after onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle) has returned, but before any saved state has been restored in to the view. This gives subclasses a chance to initialize themselves once they know their view hierarchy has been completely created. The fragment's view hierarchy is not however attached to its parent at this point.
For instance:
class SongListFragment : Fragment() {
private var _binding: FragmentSongsListBinding? = null
private val binding get() = _binding!! //error
private var player: ExoPlayer? = null
private lateinit var inputManager: InputMethodManager
private lateinit var sharedPreferences: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
private val songListAdapter by lazy {
SongListAdapter(requireActivity()) { show ->
showMultipleSelectActionBar(
show
)
}
}
private val viewModel: SongListViewModel by activityViewModels()
#SuppressLint("RtlHardcoded")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
player = (context as MainActivity).exoPlayer
inputManager =
requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
editor = sharedPreferences.edit()
enterTransition = Slide(Gravity.END)
exitTransition = Fade()
returnTransition = Fade()
}
#RequiresApi(Build.VERSION_CODES.Q)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
requireActivity().window.statusBarColor =
ContextCompat.getColor(requireContext(), R.color.main_background_color)
if (_binding == null) {
_binding = FragmentSongsListBinding.inflate(inflater, container, false)
}
postponeEnterTransition()
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.root.doOnPreDraw { startPostponedEnterTransition() }
viewModel.getSongList(context)
initObserver()
setDataToView()
searchMusic()
multipleSelectButtonImplementation()
if (sharedPreferences.getInt(Constants.AD_COUNTER, 0) == 3) {
(activity as MainActivity).showAd(requireContext())
editor.putInt(Constants.AD_COUNTER, 0)
editor.commit()
} else {
editor.putInt(Constants.AD_COUNTER, (sharedPreferences.getInt(Constants.AD_COUNTER, 0) + 1))
editor.commit()
}
}
I'm using peerjs to make video calls, but I notice that It needs to keep tapping on the screen to update the video on webview. also, it's not auto-playing video until I touch it, after touching on screen I can see an updated frame but again it looks like feezed.
I have setup webview code as below,
private fun setupWebView() {
binding.webView.webChromeClient = object: WebChromeClient() {
override fun onPermissionRequest(request: PermissionRequest?) {
request?.grant(request.resources)
}
}
binding.webView.settings.javaScriptEnabled = true
binding.webView.settings.mediaPlaybackRequiresUserGesture = false
binding.webView.addJavascriptInterface(JSInterface(this), "Android")
binding.webView.webViewClient = object: WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
initializePeer()
}
}
loadWebApp()
}
private fun loadWebApp() {
val filePath = "file:///android_asset/peerjs/call.html"
binding.webView.loadUrl(filePath)
}
Please help on this
My question is about Mapbox. In this period I am working on an ANDROID application based on mapbox, using Kotlin and Fragments and my problem concerns the visualization of points on the map itself. That is my need is to be able to show points on the map through a GEOJSON file, for now I have been able to see the map in full in the application, but I cannot find a way to show the points taken from a GeoJson file and locate myself in the map via a button.
I should implement both functions in the fragment, so my problem is precisely that of not being able to show the points of a geojson file and find a way to locate myself in the map itself. I await help if there is someone able to help me with this problem, I also leave the code of the fragment class in kotlin.
Thanks everyone in advance !!
FRAGMENT HOME
class HomeFragment : Fragment() {
private var mapView: MapView? = null
#Nullable
override fun onCreateView(
inflater: LayoutInflater,
#Nullable container: ViewGroup?,
#Nullable savedInstanceState: Bundle?
): View? {
Mapbox.getInstance(
context!!.applicationContext,
"MIO CODICE MAPBOX"
)
val view: View = inflater.inflate(R.layout.fragment_home, container, false)
mapView = view.findViewById<View>(R.id.mapView) as MapView
mapView!!.onCreate(savedInstanceState)
return view
}
override fun onResume() {
super.onResume()
mapView!!.onResume()
}
override fun onPause() {
super.onPause()
mapView!!.onPause()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView!!.onSaveInstanceState(outState)
}
override fun onLowMemory() {
super.onLowMemory()
mapView!!.onLowMemory()
}
override fun onDestroyView() {
super.onDestroyView()
mapView!!.onDestroy()
}
}
HOST ACTIVITY :
class HostActivity : AppCompatActivity() {
lateinit var googleSignInClient: GoogleSignInClient
private lateinit var navController: NavController
private val mAuth: FirebaseAuth = FirebaseAuth.getInstance()
private val db: FirebaseFirestore = FirebaseFirestore.getInstance()
private lateinit var drawerLayout: DrawerLayout
private lateinit var navViewBinding: DrawerHeaderLayoutBinding
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_host)
val toolbar = customToolbar
setSupportActionBar(toolbar)
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_id))
.requestEmail()
.build()
googleSignInClient = GoogleSignIn.getClient(this, gso)
drawerLayout = drawer_layout
navViewBinding = DrawerHeaderLayoutBinding.inflate(layoutInflater, navView, true)
val navHost =
supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment
navController = navHost.navController
val navInflater = navController.navInflater
val graph = navInflater.inflate(R.navigation.main_graph)
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == R.id.onBoarding ||
destination.id == R.id.authFragment ||
destination.id == R.id.loginFragment ||
destination.id == R.id.signUpFragment
) {
toolbar.visibility = View.GONE
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
} else {
toolbar.visibility = View.VISIBLE
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
}
}
if (!Prefs.getInstance(this)!!.hasCompletedWalkthrough!!) {
if (mAuth.currentUser == null) {
graph.startDestination = R.id.authFragment
} else {
getUserData()
graph.startDestination = R.id.homeFragment
}
} else {
graph.startDestination = R.id.onBoarding
}
navController.graph = graph
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
navView.setupWithNavController(navController)
navView.setNavigationItemSelectedListener {
it.isChecked
drawerLayout.closeDrawers()
when (it.itemId) {
R.id.action_logout -> {
MyApplication.currentUser!!.active = false
FirestoreUtil.updateUser(MyApplication.currentUser!!) {
mAuth.signOut()
}
googleSignInClient.signOut()
MyApplication.currentUser = null
navController.navigate(R.id.action_logout)
}
}
true
}
}
private fun getUserData() {
val ref = db.collection("users").document(mAuth.currentUser!!.uid)
ref.get().addOnSuccessListener {
val userInfo = it.toObject(UserModel::class.java)
navViewBinding.user = userInfo
MyApplication.currentUser = userInfo
MyApplication.currentUser!!.active = true
FirestoreUtil.updateUser(MyApplication.currentUser!!) {
}
}.addOnFailureListener {
val intent = Intent(this, MyApplication::class.java)
startActivity(intent)
finish()
}
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController, drawerLayout)
}
}
You'll need to use a GeoJsonSource. https://github.com/mapbox/mapbox-android-demo/search?q=GeoJsonSource shows how the demo app uses the source.
https://github.com/mapbox/mapbox-android-demo/blob/master/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/basics/KotlinSupportMapFragmentActivity.kt (Its XML layout file)
Putting icons on the map. https://docs.mapbox.com/android/maps/examples/marker-symbol-layer/. Do all of the icon setup inside of the fragment's onStyleLoaded() callback as seen at https://github.com/mapbox/mapbox-android-demo/blob/master/MapboxAndroidDemo/src/main/java/com/mapbox/mapboxandroiddemo/examples/basics/KotlinSupportMapFragmentActivity.kt#L51-L55
https://github.com/mapbox/mapbox-android-demo/search?q=loadGeojson shows how the demo app loads from a GeoJson file. You could use coroutines instead of building out the AsyncTask.
Although it's in Java, https://docs.mapbox.com/android/maps/examples/show-a-users-location-on-a-fragment/ shows how to combine the Maps SDK's LocationComponent with a fragment.
Regarding moving the camera to the device's last known location when a button is clicked, see my answer at https://stackoverflow.com/a/64159178/6358488
I'm uploading some photos to my server, and want to view the progress of individual uploads. However, my RecyclerView isn't updating.
I know the progress is being updated, as I can see the progress in the my upload task, and the photo is uploaded to the server. I thought the viewholder would be what I would use to update the view, but my progress listener isn't' called.
Does anyone see what I have done wrong? Or could do better?
The PhotoUploadItem is the object that gets updated with the progress.
class PhotoUploadItem(var photoStore: PhotoStore) {
interface PhotoUploadProgressListener {
fun onProgressChanged(progress: Int)
}
private var uploadProgress = 0
private var listener: PhotoUploadProgressListener? = null
fun setUploadProgress(uploadProgress: Int) {
this.uploadProgress = uploadProgress
listener?.onProgressChanged(uploadProgress)
}
fun subscribe(listener: PhotoUploadProgressListener) {
this.listener = listener
}
fun unsubscribe() {
this.listener = null
}
fun progress() = uploadProgress
fun identifier(): Int = photoStore.key
}
The PhotoUploadsAdapter takes a list of them.
class PhotoUploadsAdapter : RecyclerView.Adapter<PhotoUploadsAdapter.CustomViewHolder>() {
inner class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
private val differCallback = object : DiffUtil.ItemCallback<PhotoUploadItem>() {
override fun areItemsTheSame(oldItem: PhotoUploadItem, newItem: PhotoUploadItem) = oldItem.identifier() == newItem.identifier()
override fun areContentsTheSame(oldItem: PhotoUploadItem, newItem: PhotoUploadItem) = oldItem.hashCode() == newItem.hashCode()
}
val differ = AsyncListDiffer(this, differCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
return CustomViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_photo_upload, parent, false))
}
override fun getItemCount() = differ.currentList.size
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val item = differ.currentList[position]
holder.itemView.apply {
///..... set other fields
tvDisplayName?.text = item.photoStore.displayName
// THIS DOESN'T WORK
item.subscribe(object : PhotoUploadItem.PhotoUploadProgressListener{
override fun onProgressChanged(progress: Int) {
Log.i("UploadViewHolder", "${item.identifier()} has progress: $progress")
progressBar?.progress = progress
tvProgressAmount?.text = String.format(Locale.ROOT, "%d%%", progress)
}
})
}
}
}
I tried to add groupie adapter to my recycler view as shown in the code below. However when i run my application, i get a compiler error saying my reyclerview cannot be null (because of kotlin null safe feature). I cant figure out why this adapter is not working.
I know the problem is in the line :
recyclerview_newmessage.adapter = adapter
Here's the full code
class NewMessageActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.title = "Select User"
fetchUsers()
}
private fun fetchUsers() {
val ref = FirebaseDatabase.getInstance().getReference("/users")
ref.addListenerForSingleValueEvent(object: ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
val adapter = GroupAdapter<ViewHolder>()
p0.children.forEach {
Log.d("NewMessage", it.toString())
val user = it.getValue(User::class.java)
if (user != null) {
adapter.add(UserItem(user))
}
}
recyclerview_newmessage.adapter = adapter
}
})
}
}
class UserItem(val user: User): Item<ViewHolder>() {
override fun bind(viewHolder: ViewHolder, position: Int) {
viewHolder.itemView.username_textview_new_message.text = user.username
}
override fun getLayout(): Int {
return R.layout.user_row_new_message
}
}
The final result show display the rows in the recycler view with the usernames that are in my firebase database.
If you define the recycler view in the xml file then you need to add it.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.ref_xml_file) . // add this line
supportActionBar?.title = "Select User"
fetchUsers()
}
if not define into the xml file.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var recyclerview_newmessage = RecyclerView(this)
setContentView(recyclerview_newmessage) . // add this line
supportActionBar?.title = "Select User"
fetchUsers()
}