Bind custom DialogFragment to service initialized into Application scope - java

I'm working to an application that makes some webcalls and I want to add a DialogFragment (with a ProgressBar at the center) that shows when I'm waiting for the call to complete.
Webcalls are extended from AsyncTask with a private library that gives an interface (DialogInterface) to set what to do when the dialog it's shown and hidden.
For the DialogFragment I've found some nice code from here:
https://code.luasoftware.com/tutorials/android/android-show-loading-and-prevent-touch/
It works without problems from an Activity class, but the DialogInterface has to be set to the WebService builder which is initialized into the Application class
This is the init sequence in the Application class
WebService.init(
WebServiceBuilder(this)
.setBaseUrlTest("http://google.it/")
.setUrlType(UrlType.TEST)
.setDialogInterface(object : DialogInterface {
override fun showDialog(context: Context?) {
if (context != null)
val busyDialog = BusyDialogFragment.show(supportFragmentManager)
}
override fun hideDialog() {
//code that hide the DialogFragment
}
})
Obviously in the Application class I cannot get the FragmentManager so this code doesn't works.

You can register an ActivityLifecycleCallbacks in the Application class to know which activity is currently running, and get the FragmentManager:
class MyApplication : Application() {
private var currentActivityRef : WeakReference<FragmentActivity?>? = null
private val supportFragmentManager : FragmentManager?
get() = currentActivityRef?.get()?.supportFragmentManager
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityStarted(activity: Activity?) {
currentActivityRef = WeakReference(activity as? FragmentActivity)
}
override fun onActivityStopped(activity: Activity?) {
currentActivityRef = null
}
override fun onActivityPaused(activity: Activity?) {}
override fun onActivityResumed(activity: Activity?) {}
override fun onActivityDestroyed(activity: Activity?) {}
override fun onActivitySaveInstanceState(activity: Activity?,
outState: Bundle?) {
}
override fun onActivityCreated(activity: Activity?,
savedInstanceState: Bundle?) {
}
})
}
}

Related

Fatal Exception: java.lang.NullPointerException crash event [duplicate]

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()
}
}

System services not available to Activities before onCreate() [KOTLIN]

I have hideKeyboard(view: View) method in util class. In my activity I want to call that method. I created object in activity utils = Utils() and then utils.HideKeyboard(binding.authConstraint) but when Im trying to click on constraintLayout it is throwing error "System services not available to Activities before onCreate()" what am I doing wrong ?
my Util Class
open class Utils():Activity(){
fun hideKeyboard(view: View) {
val inputMethodManager =
getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
}
my activity
class AuthActivity : ActivityWithDisposableList() {
private lateinit var binding: ActivityAuthBinding
private lateinit var authViewModel: AuthViewModel
val utils = Utils()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_auth)
authViewModel = ViewModelProviders.of(this).get(AuthViewModel::class.java)
binding.authViewModel = authViewModel
binding.lifecycleOwner = this
authViewModel.isKeyboardClosed.observe(this, Observer { isTrue ->
if (isTrue) {
utils.hideKeyboard(binding.authConstraint,this)
binding.usernameInputEditText.clearFocus()
binding.passwordInputEditText.clearFocus()
}
})
}
}
Do not create an instance of an Activity subclass yourself. And do not make some arbitrary class extend Activity, just because you need a Context. Get the Context from a parameter to a constructor or function.
With that in mind, replace your Utils with:
class Utils {
fun hideKeyboard(view: View) {
val inputMethodManager =
view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
}
}
Here, you are getting the Context from the View that you are supplying.
Note that you could also go with object Utils or have hideKeyboard() be an extension function on View, if you liked.

Implement custom file picker dialog in Android

I have bottomsheet dialog for choosing file but, i don't know how to implement file picker in it, i want to make as in screenshot below.I have been looking for answer but i found only some libraries but it is simple dialog not bottomsheet.
Please Help.Thanks in advance
You can use Simple Storage's file picker. It supports scoped storage.
class MainActivity : AppCompatActivity() {
private val storageHelper = SimpleStorageHelper(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
storageHelper.onFolderSelected = { requestCode, folder ->
// do stuff
}
storageHelper.onFileSelected = { requestCode, file ->
// do stuff
}
btnOpenFolderPicker.setOnClickListener { storageHelper.openFolderPicker() }
btnOpenFilePicker.setOnClickListener { storageHelper.openFilePicker() }
}
override fun onSaveInstanceState(outState: Bundle) {
storageHelper.onSaveInstanceState(outState)
super.onSaveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
storageHelper.onRestoreInstanceState(savedInstanceState)
}
}

How to notify and update list when item has been deleted using Groupie RecyclerView library and Kotlin

I have a RecyclerView implemented with the Groupie library and I can delete an item from the list fine, however need to update the view to see the change. I'd like to have something like notifyDataSetChanged() instead, so the list updates immediately. I'm a bit confused at this stage though, tried a few different ways to get an interface from the class that hosts my view holder to be triggered from the fragment that holds the adapter but I think I'm stuck now if I could get some help please.
class RecyclerProductItem(
private val activity: MainActivity,
private val product: Product, private val adapterListener: AdapterListener
) : Item<GroupieViewHolder>() {
companion object {
var clickListener: AdapterListener? = null
}
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.apply {
with(viewHolder.itemView) {
clickListener = adapterListener
ivTrash.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
if (clickListener != null) {
Toast.makeText(context, "delete method to be added here", Toast.LENGTH_SHORT).show()
clickListener?.onClickItem(position)
}
}
})
}
}
}
override fun getLayout() = R.layout.recyclerview_item_row
interface AdapterListener {
fun onClickItem(position: Int)
}
}
Here it's my fragment. I tried to add a section to the adapter to see if it would allow me to retrieve a listener for it, but as my listener should be triggered under a specific item within the layout, this may not be the best solution, although couldn't make this work either.
class ProductsListFragment : Fragment(), RecyclerProductItem.AdapterListener {
private lateinit var adapter: GroupAdapter<GroupieViewHolder>
private val section = Section()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_products_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val linearLayoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = linearLayoutManager
adapter = GroupAdapter()
adapter.add(section)
recyclerView.adapter = adapter
loadProducts()
}
private fun loadProducts() {
GetProductsAPI.postData(object : GetProductsAPI.ThisCallback {
override fun onSuccess(productList: List<JsonObject>) {
for (jo in productList) {
val gson = GsonBuilder().setPrettyPrinting().create()
val product: Product =
gson.fromJson(jo, Product::class.java)
adapter.add(
RecyclerProductItem(
activity as MainActivity,
Product(
product.id,
product.title,
product.description,
product.price
),adapterListenerToBePassedHere
)
) // This part is where I should be giving the listener, but get a red line since not sure how to get it to include it here.
}
}
})
}
companion object {
fun newInstance(): ProductsListFragment {
return ProductsListFragment()
}
}
override fun onClickItem(position: Int) {
adapter.notifyItemRemoved(position)
}
}
Many thanks.
I think you are missing this concept from the groupie Readme:
Modifying the contents of the GroupAdapter in any way automatically sends change notifications. Adding an item calls notifyItemAdded(); adding a group calls notifyItemRangeAdded(), etc.
So to remove an item, call section.remove(item). However, in your onClickItem function you currently only pass the position. Pass the item like clickListener?.onClickItem(this#RecyclerProductItem) instead.
Even more ideally and safely you should remove by product.id, e.g. clickListener?.onClickItem(this#RecyclerProductItem.product.id) then in onClickItem() you just search for the item with that product id and remove it. Let me know if I'm not clear.
Based on #carson's reply, this is what worked for me. Had to add the items to the section, the section to the adapter and then remove the item from the section based on the adapter position once that listener is clicked, passing the method that implements the listener as one of the arguments to complete the GroupAdapter.
class RecyclerProductItem(
private val activity: MainActivity,
private val product: Product, private val adapterListener: AdapterListener
) : Item<GroupieViewHolder>() {
companion object {
var clickListener: AdapterListener? = null
}
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.apply {
with(viewHolder.itemView) {
tvTitle.text = product.title
clickListener = adapterListener
ivTrash.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
if (clickListener != null) {
Toast.makeText(context, "delete method to be added here", Toast.LENGTH_SHORT).show()
clickListener?.onClickItem(this#RecyclerProductItem.product.id, adapterPosition)
}
}
})
}
}
}
override fun getLayout() = R.layout.recyclerview_item_row
interface AdapterListener {
fun onClickItem(id: Int, position: Int)
}
}
And
private fun loadProducts() {
GetProductsAPI.postData(object : GetProductsAPI.ThisCallback,
RecyclerProductItem.AdapterListener {
override fun onSuccess(productList: List<JsonObject>) {
Log.i(LOG_TAG, "onSuccess $LOG_TAG")
for (jo in productList) {
val gson = GsonBuilder().setPrettyPrinting().create()
val product: Product =
gson.fromJson(jo, Product::class.java)
val linearLayoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = linearLayoutManager
adapter = GroupAdapter()
section.add(
RecyclerProductItem(
activity as MainActivity,
Product(
product.id,
product.title,
product.description,
product.price
), this
)
)
adapter.add(section)
recyclerView.adapter = adapter
}
}
override fun onFailure() {
Log.e(LOG_TAG, "onFailure $LOG_TAG")
}
override fun onError() {
Log.e(LOG_TAG, "onError $LOG_TAG")
}
override fun onClickItem(id: Int, position: Int) {
section.remove(adapter.getItem(position))
}
})
}

RecyclerView adapter not working with groupie

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()
}

Categories