I am writing an pp that requires to display text on the screen of the mobile. The text should be having the following characteristics:
The window should be transparent and without background
The touch events should be passed to underlying apps or icons
It should be floating over other windows
I have written the following code:
For the first requirement
File: styles.xml:
<style name="Theme.AppCompat.Transparent.NoActionBar"
parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">#android:color/transparent</item>
<item name="android:windowContentOverlay">#null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
For the second requirement
File: MainActivity.kt
package com.example.transparentwindow
import android.graphics.Color
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.core.view.iterator
import com.example.transparentwindow.Common.Companion.toSet
class MainActivity : AppCompatActivity() {
private lateinit var textBox: TextView
private lateinit var setBox: EditText
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
if (toSet) {
return super.dispatchTouchEvent(ev)
}
else {
return true
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (toSet) {
setBox = findViewById(R.id.msg_set)
setBox.setTextColor(Color.WHITE)
setBox.setText("Type message here")
setBox.setSelection(setBox.text.length)
textBox = findViewById(R.id.msg_display)
textBox.setText("")
setBox.setBackgroundColor(Color.TRANSPARENT)
textBox.setBackgroundColor(Color.TRANSPARENT)
setBox?.setOnClickListener(
object : DoubleClickListener() {
override fun onDoubleClick(v: View?) {
// Toast.makeText(applicationContext, "Double Click", Toast.LENGTH_SHORT).show()
val textToRepeat: String = setBox.text.toString() + "\t"
val repText: String = textToRepeat.repeat(100)
textBox.setText(repText)
textBox.setTextColor(R.color.transColorBlue)
textBox.alpha = 0.1f
setBox.isVisible = false
setBox.setText("")
toSet = false
}
})
val viewGroup = findViewById<View>(android.R.id.content) as ViewGroup
val childCount = viewGroup.childCount
for( i in 0..childCount ) {
val view = viewGroup.getChildAt(i)
if (view != null) {
view.setEnabled(false)
}
}
/*
textBox?.setOnLongClickListener( object: View.OnLongClickListener {
override fun onLongClick(v: View?): Boolean {
Toast.makeText(applicationContext, "Double Click", Toast.LENGTH_SHORT).show()
return true
}
})
*/
}
}
abstract class DoubleClickListener : View.OnClickListener {
var lastClickTime: Long = 0
override fun onClick(v: View?) {
val clickTime = System.currentTimeMillis()
if (clickTime - lastClickTime < DOUBLE_CLICK_TIME_DELTA) {
onDoubleClick(v)
}
lastClickTime = clickTime
}
abstract fun onDoubleClick(v: View?)
companion object {
private const val DOUBLE_CLICK_TIME_DELTA: Long = 300 //milliseconds
}
}
}
NOTE: Added complete code
For the third requirement, I am using the feature provided by Oxygen OS in OnePlus, to run the app as a floating app
I am noticing that the window is created with the text, but the keys are not being passed to the underlying apps/icons. Any way to achieve that?
Related
The error that appears is as follows "Type mismatch: inferred type is String? but String is expected". How can I solve this problem?
The source code is as follows:
package com.example.submission2.Activity
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.example.submission2.Adapter.AdapterSectionPager
import com.example.submission2.ViewModel.DetailVM
import com.example.submission2.databinding.ActivityDetailBinding
class DetailActivity : AppCompatActivity() {
companion object{
const val EXTRA_USERNAME = "extra_username"
}
private lateinit var binding: ActivityDetailBinding
private lateinit var viewModel: DetailVM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.apply {
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
val username = intent.getStringExtra(EXTRA_USERNAME)
viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(DetailVM::class.java)
viewModel.setPenggunaDetail(username)
viewModel.getPenggunaDetail().observe(this) {
if (it != null) {
binding.apply {
tvNamaDetail.text = it.name
tvUsernameDetail.text = it.login
tvCompanyDetail.text = it.company
tvEmailDetail.text = it.email
tvFollowersDetail.text = "${it.followers} Followers"
tvFollowingDetail.text = "${it.following} Follwing"
Glide.with(this#DetailActivity)
.load(it.avatar_url)
.transition(DrawableTransitionOptions.withCrossFade())
.centerCrop()
.into(ivDetailProfil)
}
}
}
val sectionPagerAdpter = AdapterSectionPager(this,supportFragmentManager)
binding.apply {
viewPager.adapter = sectionPagerAdpter
tabs.setupWithViewPager(viewPager)
}
}
}
error appears on the line "viewModel.set User Data(username)" username is used in extra_username which will be called in main
for main activity as follows:
package com.example.submission2.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.KeyEvent
import android.view.View
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.submission2.Adapter.AdapterPengguna
import com.example.submission2.DataBase.Pengguna
import com.example.submission2.R
import com.example.submission2.ViewModel.MainVM
import com.example.submission2.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var viewModel: MainVM
private lateinit var adapter: AdapterPengguna
private fun searchPengguna(){
binding.apply {
val query = etSearch.text.toString()
if (query.isEmpty())return
showLoading(true)
viewModel.setSearchPengguna(query)
}
}
private fun showLoading(state: Boolean){
if (state){
binding.progressBarMain.visibility = View.VISIBLE
}else{
binding.progressBarMain.visibility = View.GONE
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
adapter = AdapterPengguna()
adapter.notifyDataSetChanged()
adapter.setOnItemClickCallback(object :AdapterPengguna.OnItemClickCallback{
override fun onItemCliked(data: Pengguna) {
Intent(this#MainActivity,DetailActivity::class.java).also {
it.putExtra(DetailActivity.EXTRA_USERNAME, data.login)
startActivity(it)
}
}
})
viewModel = ViewModelProvider(this,ViewModelProvider.NewInstanceFactory()).get(MainVM::class.java)
binding.apply {
rvPengguna.layoutManager = LinearLayoutManager(this#MainActivity)
rvPengguna.setHasFixedSize(true)
rvPengguna.adapter = adapter
btnSearch.setOnClickListener {
searchPengguna()
}
etSearch.setOnKeyListener { v, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER){
searchPengguna()
return#setOnKeyListener true
}
return#setOnKeyListener false
}
}
viewModel.getSearchPengguna().observe(this,{
if (it!= null){
adapter.setList(it)
showLoading(false
)
}
})
}
}
In your code there is no such line as viewModel.setUserData
I guess that the error occurs in the line viewModel.setPenggunaDetail(username)
In this case, you should pay attention to the fact that the all intent.getExtra calls returns nullable values.
Thus, if the setPenggunaDetail call expects a non-nullable argument, you must first check username value for null
In my app, when I click on an item (Issue) in a RecyclerView which uses FirebaseRecyclerPagingAdapter to paginate data from Firebase realtime database, it displays details about the item clicked in another fragment (using navigation component). This works fine on the first click, however when I return to the previous fragment and click the same item on the RecyclerView a second time, the details of the item are not shown.
Because I use safe args to pass the item id (issueId) to the next fragment which it uses to query the firebase realtime database and retrieve the details to be displayed, I decide to log the item id to my console in onViewCreated() just to be sure that the item id is being passed on the second click and also that the details (names of user who added an issue) are being retrieved from the database, but just not showing. Then, I noticed a weird behaviour.
On the first click, the item id is logged to the console, the details are logged to the console as well and the fragment displays the details. However on the second click, the item id is logged to the console (showing that the item id is being passed as should be the case), but the details are not logged to the console and not displayed in the fragment (hence the fragment shows up empty). Now the weird part, when I navigate back to the previous fragment, then I see a log of the details displayed twice.
Another strange thing I noticed is that, every item on the RecyclerView has this weird behaviour except the last item. The last item displays its details on the second click, but any other item I click doesn't.
I also noticed that the log shows the details for every item I have previously clicked twice when I navigate back even though I am clicking on a different item
I changed the adapter from FirebaseRecyclerPagingAdapter to FirebaseRecyclerAdapter, everything works fine. When I change back to using FirebaseRecyclerPagingAdapter, the same problem exists.
Is this a bug in my code or FirebaseRecyclerPagingAdapter itself. What could be the problem and what can I do to fix it?
Below is the FirebaseRecyclerPagingAdapter:
package com.colley.android.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.colley.android.R
import com.colley.android.databinding.ItemIssueBinding
import com.colley.android.model.Issue
import com.colley.android.model.Profile
import com.firebase.ui.database.paging.DatabasePagingOptions
import com.firebase.ui.database.paging.FirebaseRecyclerPagingAdapter
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.ValueEventListener
import com.google.firebase.database.ktx.database
import com.google.firebase.database.ktx.getValue
import com.google.firebase.ktx.Firebase
class IssuesPagingAdapter(
options: DatabasePagingOptions<Issue>,
private val context: Context,
private val currentUser: FirebaseUser?,
private val clickListener: IssuePagingItemClickedListener
) : FirebaseRecyclerPagingAdapter<Issue, IssuePagingViewHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IssuePagingViewHolder {
val viewBinding = ItemIssueBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return IssuePagingViewHolder(viewBinding)
}
override fun onBindViewHolder(viewHolder: IssuePagingViewHolder, position: Int, model: Issue) {
viewHolder.bind(currentUser, model, context, clickListener)
}
interface IssuePagingItemClickedListener {
fun onItemClick(issueId: String, view: View)
fun onItemLongCLicked(issueId: String, view: View)
fun onUserClicked(userId: String, view: View)
}
}
class IssuePagingViewHolder (private val itemBinding : ItemIssueBinding) : RecyclerView.ViewHolder(itemBinding.root) {
#SuppressLint("SetTextI18n")
fun bind(
currentUser: FirebaseUser?,
issue: Issue, context: Context,
clickListener: IssuesPagingAdapter.IssuePagingItemClickedListener) = with(itemBinding) {
//set issue title, body, timeStamp, contributions and endorsements count
issueTitleTextView.text = issue.title
issueBodyTextView.text = issue.body
issueTimeStampTextView.text = issue.timeStamp
contributionsTextView.text = issue.contributionsCount.toString()
endorsementTextView.text = issue.endorsementsCount.toString()
//check if userId is not null
issue.userId?.let { userId ->
//retrieve user profile
Firebase.database.reference.child("profiles").child(userId)
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val profile = snapshot.getValue<Profile>()
if (profile != null) {
//set the name of user who raised this issue
userNameTextView.text = profile.name
//set the school of the user who raised this issue
userSchoolTextView.text = profile.school
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
//retrieve user photo
Firebase.database.reference.child("photos").child(userId)
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val photo = snapshot.getValue<String>()
//set photo
if (photo != null) {
Glide.with(root.context).load(photo)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).into(userImageView)
} else {
Glide.with(root.context).load(R.drawable.ic_person).into(userImageView)
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
}
root.setOnClickListener {
if(issue.issueId != null) {
clickListener.onItemClick(issue.issueId, it)
}
}
root.setOnLongClickListener {
if(issue.issueId != null) {
clickListener.onItemLongCLicked(issue.issueId, it)
}
true
}
userNameTextView.setOnClickListener {
if(issue.userId != null) {
clickListener.onUserClicked(issue.userId, it)
}
}
}
}
Here is the fragment to display the item details:
package com.colley.android.view.fragment
import android.os.Bundle
import android.util.Log
import android.view.*
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.colley.android.R
import com.colley.android.adapter.IssuesCommentsRecyclerAdapter
import com.colley.android.databinding.FragmentViewIssueBinding
import com.colley.android.model.Comment
import com.colley.android.model.Issue
import com.colley.android.model.Profile
import com.colley.android.view.dialog.IssueCommentBottomSheetDialogFragment
import com.firebase.ui.database.FirebaseRecyclerOptions
import com.firebase.ui.database.ObservableSnapshotArray
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.auth.ktx.auth
import com.google.firebase.database.*
import com.google.firebase.database.ktx.database
import com.google.firebase.database.ktx.getValue
import com.google.firebase.ktx.Firebase
class ViewIssueFragment :
Fragment(),
IssuesCommentsRecyclerAdapter.ItemClickedListener,
IssuesCommentsRecyclerAdapter.DataChangedListener {
private val args: ViewIssueFragmentArgs by navArgs()
private var _binding: FragmentViewIssueBinding? = null
private val binding get() = _binding
private lateinit var dbRef: DatabaseReference
private lateinit var auth: FirebaseAuth
private lateinit var currentUser: FirebaseUser
private lateinit var recyclerView: RecyclerView
private lateinit var commentSheetDialog: IssueCommentBottomSheetDialogFragment
private var issue: Issue? = null
private var adapter: IssuesCommentsRecyclerAdapter? = null
private var manager: LinearLayoutManager? = null
private val uid: String
get() = currentUser.uid
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentViewIssueBinding.inflate(inflater, container, false)
recyclerView = binding?.issuesCommentsRecyclerView!!
return binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//initialize Realtime Database
dbRef = Firebase.database.reference
//initialize authentication
auth = Firebase.auth
//initialize currentUser
currentUser = auth.currentUser!!
//log item id
Log.d("Log itemId", args.issueId)
//get a query reference to issue comments //order by time stamp
val commentsRef = dbRef.child("issues").child(args.issueId)
.child("comments").orderByChild("commentTimeStamp")
//the FirebaseRecyclerAdapter class and options come from the FirebaseUI library
//build an options to configure adapter. setQuery takes firebase query to listen to and a
//model class to which snapShots should be parsed
val options = FirebaseRecyclerOptions.Builder<Comment>()
.setQuery(commentsRef, Comment::class.java)
.build()
//initialize issue comments adapter
adapter = IssuesCommentsRecyclerAdapter(
options,
currentUser,
this,
this,
requireContext())
manager = LinearLayoutManager(requireContext())
//reversing layout and stacking fron end so that the most recent comments appear at the top
manager?.reverseLayout = true
manager?.stackFromEnd = true
recyclerView.layoutManager = manager
recyclerView.adapter = adapter
dbRef.child("issues").child(args.issueId).addValueEventListener(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
issue = snapshot.getValue<Issue>()
if(issue != null) {
//listener for contrbutions count used to set count text
dbRef.child("issues").child(args.issueId)
.child("contributionsCount").addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val count = snapshot.getValue<Int>()
if(count != null) {
binding?.contributionsTextView?.text = count.toString()
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
//listener for endorsement counts used to set endorsement count text
dbRef.child("issues").child(args.issueId)
.child("endorsementsCount").addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val count = snapshot.getValue<Int>()
if(count != null) {
binding?.endorsementTextView?.text = count.toString()
}
}
override fun onCancelled(error: DatabaseError) {} }
)
//set issue title, body and time stamp, these don't need to change
binding?.issueTitleTextView?.text = issue?.title
binding?.issueBodyTextView?.text = issue?.body
binding?.issueTimeStampTextView?.text = issue?.timeStamp.toString()
//listener for user photo
dbRef.child("photos").child(issue?.userId.toString())
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val photo = snapshot.getValue<String>()
if(photo != null) {
context?.let { context -> binding?.userImageView?.let {
imageView ->
Glide.with(context).load(photo).into(
imageView
)
} }
} else {
context?.let { context -> binding?.userImageView?.let {
imageView ->
Glide.with(context).load(R.drawable.ic_profile).into(
imageView
)
} }
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
//listener for profile to set name and school
dbRef.child("profiles").child(issue?.userId.toString())
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val profile = snapshot.getValue<Profile>()
if (profile != null) {
//log name details to console
profile.name?.let { Log.d("Log Details", it) }
binding?.userNameTextView?.text = profile.name
binding?.userSchoolTextView?.text = profile.school
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
binding?.commentLinearLayout?.setOnClickListener {
commentSheetDialog = IssueCommentBottomSheetDialogFragment(
requireContext(),
requireView())
commentSheetDialog.arguments = bundleOf("issueIdKey" to args.issueId)
commentSheetDialog.show(parentFragmentManager, null)
}
binding?.endorseLinearLayout?.setOnClickListener {
//update contributions count
dbRef.child("issues").child(args.issueId).child("endorsementsCount")
.runTransaction(
object : Transaction.Handler {
override fun doTransaction(currentData: MutableData): Transaction.Result {
//retrieve the current value of endorsement count at this location
var endorsementsCount = currentData.getValue<Int>()
if (endorsementsCount != null) {
//increase the count by 1
endorsementsCount++
//reassign the value to reflect the new update
currentData.value = endorsementsCount
}
//set database issue value to the new update
return Transaction.success(currentData)
}
override fun onComplete(
error: DatabaseError?,
committed: Boolean,
currentData: DataSnapshot?
) {
if (error == null && committed) {
Toast.makeText(requireContext(), "Endorsed", Toast.LENGTH_SHORT)
.show()
}
}
}
)
}
//view profile when clicked
binding?.userImageView?.setOnClickListener {
val action = issue?.userId?.let { it1 ->
ViewIssueFragmentDirections.actionViewIssueFragmentToUserInfoFragment(it1)
}
if (action != null) {
parentFragment?.findNavController()?.navigate(action)
}
}
//view user profile when clicked
binding?.userNameTextView?.setOnClickListener {
val action = issue?.userId?.let { it1 ->
ViewIssueFragmentDirections.actionViewIssueFragmentToUserInfoFragment(it1)
}
if (action != null) {
parentFragment?.findNavController()?.navigate(action)
}
}
}
override fun onItemClick(comment: Comment, view: View) {
//expand comment
}
override fun onItemLongCLicked(comment: Comment, view: View) {
//create option to delete
//create option to respond
}
//view user profile
override fun onUserClicked(userId: String, view: View) {
val action = ViewIssueFragmentDirections.actionViewIssueFragmentToUserInfoFragment(userId)
parentFragment?.findNavController()?.navigate(action)
}
override fun onStart() {
super.onStart()
adapter?.startListening()
}
override fun onStop() {
super.onStop()
adapter?.stopListening()
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
override fun onDataAvailable(snapshotArray: ObservableSnapshotArray<Comment>) {
//dismiss progress bar once snapshot is available
binding?.issuesCommentProgressBar?.visibility = GONE
//show that there are no comments if snapshot is empty else hide view
//show recycler view if snapshot is not empty else hide
if (snapshotArray.isEmpty()) {
binding?.noCommentsLayout?.visibility = VISIBLE
} else {
binding?.noCommentsLayout?.visibility = GONE
binding?.issuesCommentsRecyclerView?.visibility = VISIBLE
}
}
}
Here is the fragment with the recyclerView showing how I have initialised the adapter:
package com.colley.android.view.fragment
import android.os.Bundle
import android.view.*
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.paging.LoadState
import androidx.paging.PagingConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.colley.android.R
import com.colley.android.adapter.IssuesPagingAdapter
import com.colley.android.databinding.FragmentIssuesBinding
import com.colley.android.model.Issue
import com.firebase.ui.database.paging.DatabasePagingOptions
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.auth.ktx.auth
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ktx.database
import com.google.firebase.ktx.Firebase
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class IssuesFragment :
Fragment(),
IssuesPagingAdapter.IssuePagingItemClickedListener {
private var _binding: FragmentIssuesBinding? = null
private val binding get() = _binding!!
private lateinit var dbRef: DatabaseReference
private lateinit var auth: FirebaseAuth
private lateinit var currentUser: FirebaseUser
private var adapter: IssuesPagingAdapter? = null
private var manager: LinearLayoutManager? = null
private lateinit var recyclerView: RecyclerView
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private val uid: String
get() = currentUser.uid
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//fragment can participate in populating the options menu
setHasOptionsMenu(true)
//initialize Realtime Database
dbRef = Firebase.database.reference
//initialize authentication
auth = Firebase.auth
//initialize currentUser
currentUser = auth.currentUser!!
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
menu.clear()
inflater.inflate(R.menu.isssues_menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.search_issues_menu_item -> {
Toast.makeText(context, "Searching issues", Toast.LENGTH_LONG).show()
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentIssuesBinding.inflate(inflater, container, false)
recyclerView = binding.issueRecyclerView
swipeRefreshLayout = binding.swipeRefreshLayout
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//get a query reference to issues
val issuesQuery = dbRef.child("issues")
//configuration for how the FirebaseRecyclerPagingAdapter should load pages
val config = PagingConfig(
pageSize = 30,
prefetchDistance = 15,
enablePlaceholders = false
)
//Options to configure an FirebasePagingAdapter
val options = DatabasePagingOptions.Builder<Issue>()
.setLifecycleOwner(viewLifecycleOwner)
.setQuery(issuesQuery, config, Issue::class.java)
.setDiffCallback(object : DiffUtil.ItemCallback<DataSnapshot>() {
override fun areItemsTheSame(
oldItem: DataSnapshot,
newItem: DataSnapshot
): Boolean {
return oldItem.getValue(Issue::class.java)?.issueId == newItem.getValue(Issue::class.java)?.issueId
}
override fun areContentsTheSame(
oldItem: DataSnapshot,
newItem: DataSnapshot
): Boolean {
return oldItem.getValue(Issue::class.java) == newItem.getValue(Issue::class.java)
}
})
.build()
//instantiate adapter
adapter = IssuesPagingAdapter(
options,
requireContext(),
currentUser,
this)
//Perform some action every time data changes or when there is an error.
viewLifecycleOwner.lifecycleScope.launch {
adapter?.loadStateFlow?.collectLatest { loadStates ->
when (loadStates.refresh) {
is LoadState.Error -> {
// The initial load failed. Call the retry() method
// in order to retry the load operation.
Toast.makeText(context, "Error fetching issues! Retrying..", Toast.LENGTH_SHORT).show()
//display no posts available at the moment
binding.noIssuesLayout.visibility = VISIBLE
adapter?.retry()
}
is LoadState.Loading -> {
// The initial Load has begun
// ...
swipeRefreshLayout.isRefreshing = true
}
is LoadState.NotLoading -> {
// The previous load (either initial or additional) completed
swipeRefreshLayout.isRefreshing = false
//remove display no posts available at the moment
binding.noIssuesLayout.visibility = GONE
}
}
when (loadStates.append) {
is LoadState.Error -> {
// The additional load failed. Call the retry() method
// in order to retry the load operation.
adapter?.retry()
}
is LoadState.Loading -> {
// The adapter has started to load an additional page
// ...
swipeRefreshLayout.isRefreshing = true
}
is LoadState.NotLoading -> {
if (loadStates.append.endOfPaginationReached) {
// The adapter has finished loading all of the data set
swipeRefreshLayout.isRefreshing = false
}
}
}
}
}
//set recycler view layout manager
manager = LinearLayoutManager(requireContext())
recyclerView.layoutManager = manager
//initialize adapter
recyclerView.adapter = adapter
swipeRefreshLayout.setOnRefreshListener {
adapter?.refresh()
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
//navigate to new fragment with issue id
override fun onItemClick(issueId: String, view: View) {
val action = HomeFragmentDirections.actionHomeFragmentToViewIssueFragment(issueId)
parentFragment?.findNavController()?.navigate(action)
}
override fun onItemLongCLicked(issueId: String, view: View) {
}
override fun onUserClicked(userId: String, view: View) {
val action = HomeFragmentDirections.actionHomeFragmentToUserInfoFragment(userId)
parentFragment?.findNavController()?.navigate(action)
}
}
Before click
After first click
After second click
Use addListenerForSingleValueEvent instead of addValueEventListener to query the item details (issue) from the database in the fragment that displays the clicked item details. Otherwise, remove the addValueEventListener in onStop() so that the listener is no longer attached to the database when navigating back to the previous fragment.
I am creating a simple application for my android tv box, which uses a webview object to show some streaming urls and choose beetween them with PGup and PGdown of a remote control (an hardware keyboard).
I am overriding method onKeyUp, but unfortunately my app seem not to detect any key press.
This is some code excerpt:
package com.dm.tutorialwebview
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.KeyEvent
import android.view.Menu
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import android.util.Log
import android.view.MotionEvent
class MainActivity : AppCompatActivity() {
var webview: WebView? = null
data class Channel(val number: Int, val name:String, val url: String )
object ChannelList {
private val list = mutableListOf<Channel>()
private var curChannel: Int = 0
[..]
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ChannelList.addItem(Channel(1,"channel1","https://...3"))
ChannelList.addItem(Channel(2,"channel2","https://..."))
ChannelList.addItem(Channel(3,"channel3","https://..."))
webview = findViewById(R.id.myweb)
webview!!.webViewClient = WebViewClient()
webview!!.settings.javaScriptEnabled = false
webview!!.webChromeClient = WebChromeClient()
webview!!.settings.domStorageEnabled = true
webview!!.settings.builtInZoomControls = false
webview!!.settings.setSupportZoom(false)
webview!!.overScrollMode = WebView.OVER_SCROLL_NEVER
webview!!.settings.useWideViewPort = true
webview!!.setInitialScale(1)
webview!!.loadUrl(ChannelList.getChannelUrl())
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
Log.i("TAG", "onKeyUp is been called");
return when (keyCode) {
KeyEvent.KEYCODE_PAGE_UP -> {
ChannelList.nextChannel()
webview!!.loadUrl(ChannelList.getChannelUrl())
true
}
KeyEvent.KEYCODE_PAGE_DOWN -> {
ChannelList.prevChannel()
webview!!.loadUrl(ChannelList.getChannelUrl())
true
}
KeyEvent.KEYCODE_1 -> {
ChannelList.setChannel(1)
webview!!.loadUrl(ChannelList.getChannelUrl())
true
}
else -> super.onKeyUp(keyCode, event)
}
}
}
Method onKeyUp doesn't seem to be triggered at all.
Any hints on what could be wrong with this code?
Thanks and regards
Thanks #Ashwini-violet, I replaced onKeyUp with dispathKeyEvent.
I used a workaround to limit inputs to one every 250ms; not very polite but it's working.
var lastclick : Long = 0
var keyDelay : Int = 250
[..]
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
return when (event!!.keyCode) {
KeyEvent.KEYCODE_PAGE_UP , KeyEvent.KEYCODE_DPAD_UP -> {
if ((SystemClock.currentThreadTimeMillis() - lastclick) < keyDelay) true
else {
ChannelList.nextChannel()
webview!!.clearHistory()
webview!!.clearCache(true)
showChannelName()
webview!!.loadUrl(ChannelList.getChannelUrl())
lastclick = SystemClock.currentThreadTimeMillis()
true
}
}
Below code is supposed to set "isEnabled" attribute of a button to true, but it doesn't.
I initialize a mutable list which adds a String when certain Switches are on, and remove them when are off.
I created an if condition where if the size of the list is equal to 2 then ok_button is enabled.
I can't see why the ok_button is not updated even when the conditions are met.
package com.example.malakes
import android.nfc.Tag
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.CompoundButton
import android.widget.Switch
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
import android.util.Log
import android.view.View
import android.widget.Button
class MainActivity : AppCompatActivity() {
companion object{ const val TAG = "MyActivity" } //define TAG
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val epilegmenoi: MutableList<String> = mutableListOf()
val tony = findViewById<Switch>(R.id.switchTony)
val giorgis = findViewById<Switch>(R.id.switchGiorgos)
val duke = findViewById<Switch>(R.id.switchDuke)
val nikolas = findViewById<Switch>(R.id.switchNikolas)
val dionisis = findViewById<Switch>(R.id.switchDionisis)
val grigoris = findViewById<Switch>(R.id.switchGrigoris)
val ok_button = findViewById<Button>(R.id.buttonOK)
val clear_button = findViewById<Button>(R.id.buttonCLEAR)
tony.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
epilegmenoi.add("Tony")
} else {
epilegmenoi.remove("Tony")
}
}
giorgis.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
epilegmenoi.add("Giorgis")
} else {
epilegmenoi.remove("Giorgis")
}
}
if (epilegmenoi.size == 2) {ok_button.isEnabled=true}
}
}
onCreate() is a Lifecycle method in your activity. It is only called when your Activity is being called for the first time or when phone configs change, e.g. Screen Rotation, Locale change, and ...
Setting if (epilegmenoi.size == 2) {ok_button.isEnabled=true} inside onCreate doesn't do anything for you.
Consider moving this line of code to some Event-based function.
fun updateButtonState() {
my_button.isEnabled = (myList.size == 2)
}
And inside your check box events:
tony.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
epilegmenoi.add("Tony")
} else {
epilegmenoi.remove("Tony")
}
updateButtonState()
}
giorgis.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
epilegmenoi.add("Giorgis")
} else {
epilegmenoi.remove("Giorgis")
}
updateButtonState()
}
I am trying to make an example in which when I click on a symbollayer it is expanded to know which one is pressed. I enter the data thanks to two geojson files that I created with Mapbox Studio. I tried to follow this examplehttps://docs.mapbox.com/android/maps/examples/icon-size-change-on-click/
but either none is zoomed or all of the same color are zoomed (same layer). Any ideas? What am I doing wrong? For now I am just trying to zoom those with the layer "first-layer-id".
Thank you very much.
My code here.
package com.novadev.mapboxexample.marker
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.mapbox.geojson.Feature
import com.mapbox.geojson.FeatureCollection
import com.mapbox.mapboxsdk.Mapbox
import com.mapbox.mapboxsdk.geometry.LatLng
import com.mapbox.mapboxsdk.maps.MapView
import com.mapbox.mapboxsdk.maps.MapboxMap
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback
import com.mapbox.mapboxsdk.maps.Style
import com.mapbox.mapboxsdk.style.layers.Property
import com.mapbox.mapboxsdk.style.layers.PropertyFactory
import com.mapbox.mapboxsdk.style.layers.SymbolLayer
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource
import com.novadev.mapboxexample.R
import kotlinx.android.synthetic.main.activity_marker.*
import java.net.URI
import java.net.URISyntaxException
class MarkerGeojson : AppCompatActivity(),
OnMapReadyCallback,
MapboxMap.OnMapClickListener {
private lateinit var mapboxMap: MapboxMap
private lateinit var markerAnimator: ValueAnimator
private var markerSelected = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Mapbox access token is configured here. This needs to be called either in your application
// object or in the same activity which contains the mapview.
Mapbox.getInstance(this, getString(R.string.map_box_auth_key))
// This contains the MapView in XML and needs to be called after the access token is configured.
setContentView(R.layout.activity_marker)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync(this)
initListeners()
}
override fun onMapReady(mapboxMap: MapboxMap) {
this.mapboxMap = mapboxMap
getMap()
mapboxMap.addOnMapClickListener(this)
}
override fun onMapClick(point: LatLng): Boolean {
val pixel = mapboxMap.projection.toScreenLocation(point)
val features = mapboxMap.queryRenderedFeatures(pixel,"first-layer-id")
val selectedFeature = mapboxMap.queryRenderedFeatures(
pixel, "selected-marker-layer"
)
mapboxMap.getStyle{ style->
val selectedMarkerSymbolLayer =
(style.getLayer("selected-marker-layer") as SymbolLayer)
if (selectedFeature.size > 0 && markerSelected) false
if (features.isEmpty()) if (markerSelected) {
deselectMarker(selectedMarkerSymbolLayer)
}else false
val source: GeoJsonSource? = style.getSourceAs("selected-marker")
source?.setGeoJson(
FeatureCollection.fromFeatures(
arrayOf(
Feature.fromGeometry(
features[0].geometry()
)
)
)
)
if (markerSelected) {
deselectMarker(selectedMarkerSymbolLayer)
}
if (features.size > 0) {
selectMarker(selectedMarkerSymbolLayer)
}
// Get the first feature within the list if one exist
if (features.size > 0) {
val feature = features[0]
// Ensure the feature has properties defined
for ((key, value) in feature.properties()!!.entrySet()) {
// Log all the properties
Log.d("TAG", String.format("%s = %s", key, value))
when (key) {
"NOMBRE" -> {
tvTitleMarker.text = value.toString()
cvInfo.visibility = View.VISIBLE
}
"TELEFONO" -> tvSubtitlemarker.text = value.toString()
}
}
}
}
return true
}
private fun initListeners() {
ivClose.setOnClickListener {
cvInfo.visibility = View.GONE
}
}
private fun getMap() {
mapboxMap.setStyle(
Style.MAPBOX_STREETS
) {
// Map is set up and the style has loaded. Now you can add data or make other map adjustments.
geoJSONToMap(
"first-source-id",
"first-layer-id",
"asset://madridmujeres.geojson",
it
)
geoJSONToMap(
"second-source-id",
"second-layer-id",
"asset://madridoficinascorreos.geojson",
it
)
}
}
private fun drawableToBitmap (drawable : Drawable): Bitmap {
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
var bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
var canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
private fun geoJSONToMap(
sourceId: String,
layerId: String,
asset_id: String,
style: Style) {
try {
val source = GeoJsonSource(sourceId, URI(asset_id))
style.addSource(source)
if (layerId == "first-layer-id") {
var icon = drawableToBitmap(this.resources.getDrawable(R.drawable.ic_location_purple))
style.addImage("img", icon)
val symbolLayer = SymbolLayer(layerId, sourceId)
symbolLayer.withProperties(
PropertyFactory.iconImage("img"),
PropertyFactory.iconAllowOverlap(true),
PropertyFactory.iconOffset(arrayOf(0f, -9f)),
PropertyFactory.iconAnchor(Property.ICON_ANCHOR_BOTTOM),
PropertyFactory.iconIgnorePlacement(true)
)
style.addLayer(symbolLayer)
val sourceMarker = GeoJsonSource("selected-marker")
style.addSource(sourceMarker)
val symbolLayerSelected = SymbolLayer("selected-marker-layer", "selected-marker")
symbolLayerSelected.withProperties(
PropertyFactory.iconImage("img"),
PropertyFactory.iconAllowOverlap(true),
PropertyFactory.iconOffset(arrayOf(0f, -9f)),
PropertyFactory.iconAnchor(Property.ICON_ANCHOR_BOTTOM),
PropertyFactory.iconIgnorePlacement(true))
style.addLayer(symbolLayerSelected)
} else {
style.addImage("$layerId marker", this.resources.getDrawable(R.drawable.ic_location_yellow))
val symbolLayer = SymbolLayer(layerId, sourceId)
symbolLayer.setProperties(
PropertyFactory.iconImage("$layerId marker"),
PropertyFactory.iconAllowOverlap(true),
PropertyFactory.iconAnchor(Property.ICON_ANCHOR_BOTTOM),
PropertyFactory.iconIgnorePlacement(true)
)
style.addLayer(symbolLayer)
}
} catch (e: URISyntaxException) {
e.printStackTrace()
}
}
private fun selectMarker(iconLayer: SymbolLayer) {
markerAnimator = ValueAnimator()
markerAnimator.setObjectValues(1f, 2f)
markerAnimator.duration = 300
markerAnimator.addUpdateListener { animator ->
iconLayer.setProperties(
PropertyFactory.iconSize(animator.animatedValue as Float)
)
}
markerAnimator.start()
markerSelected = true
}
private fun deselectMarker(iconLayer: SymbolLayer) {
markerAnimator.setObjectValues(2f, 1f)
markerAnimator.duration = 300
markerAnimator.addUpdateListener { animator ->
iconLayer.setProperties(
PropertyFactory.iconSize(animator.animatedValue as Float)
)
}
markerAnimator.start()
markerSelected = false
}
// Add the mapView lifecycle to the activity's lifecycle methods
public override fun onResume() {
super.onResume()
mapView!!.onResume()
}
override fun onStart() {
super.onStart()
mapView!!.onStart()
}
override fun onStop() {
super.onStop()
mapView!!.onStop()
}
public override fun onPause() {
super.onPause()
mapView!!.onPause()
}
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)
}
}
It looks as if you are not setting the GeoJSONsource for the selected marker layer.
GeoJsonSource source = style.getSourceAs("selected-marker");
if (source != null) {
source.setGeoJson(FeatureCollection.fromFeatures(
new Feature[]{Feature.fromGeometry(features.get(0).geometry())}));
}
By not setting the geoJSON source, the source will remain the geoJSON containing the FeatureCollection containing all markers. --> All markers will be expanded instead of just the one you have clicked on.