Define an extension method depending on two classes? - android

I have the following extension method defined inside MyFragment : Fragment class:
fun View.showSoftKeyboard() {
if (requestFocus()) {
val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
}
I want to extract the View.showSoftKeyboard() extension method out of the MyFragment : Fragment, so I can use it in every fragment.
However, if I make this Fragment.showSoftKeyboard(), I have no acess to the View object:
fun Fragment.showSoftKeyboard() {
if (requestFocus()) {
val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
// "this" won't be a View anymore, but a Fragment
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
}
If I leave it as View.showSoftKeyboard(), I have no access to requireActivity():
fun View.showSoftKeyboard() {
if (requestFocus()) {
// requireActivity() won't be accessible anymore
val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
}
Is there any way to define - from the outside - a extension method for a View in a Fragment, so I have access to both, the view object as well as the requireActivity() from the Fragment class?

There isn't a direct way to do this that I know of. But you can create an interface that defines the Fragment functions you need (and that Fragment already satisfies), and tack it onto your Fragments where you want to use the extension function.
interface FragmentAddendum {
fun requireActivity(): FragmentActivity
fun View.showSoftKeyboard() {
if (requestFocus()) {
val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
}
}
class MyFragment: Fragment(), FragmentAddendum {
//...
}

Is there any way to define - from the outside - a extension method for a View in a Fragment, so I have access to both, the view object as well as the requireActivity() from the Fragment class?
Not really. Extension functions are defined in such a way that they can be used in any available context. You can constraint the context in at least two ways:
using access modifiers. Private extension functions are available only in the file they were defined;
by declaring extension functions as members (the closes solution to the one you look for).
An example of the first mentioned way of constraining can look like: private fun View.newFun() {}
The second way of constraining looks like this (official Kotlin extention function example):
class Host(val hostname: String) {
fun printHostname() { print(hostname) }
}
class Connection(val host: Host, val port: Int) {
fun printPort() { print(port) }
fun Host.printConnectionString() {
printHostname() // calls Host.printHostname()
print(":")
printPort() // calls Connection.printPort()
}
fun connect() {
/*...*/
host.printConnectionString() // calls the extension function
}
}
fun main() {
Connection(Host("kotl.in"), 443).connect()
//Host("kotl.in").printConnectionString(443) // error, the extension function is unavailable outside Connection
}
More on declaring extension functions as members.
You can declare base class for each fragment and implement in that class View extension function.
Sample:
open class BaseFragment: Fragment() {
fun View.showSoftKeyboard() {
if (requestFocus()) {
// requireActivity() won't be accessible anymore
val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
}
}
IMHO, the better solution is to avoid any class inheritance or interface implementation. Modify your Fragment extension function to get as an argument a View:
// Optionally set default value as getView(). Depends on your needs.
fun Fragment.showSoftKeyboard(view: View) {
if (view.requestFocus()) {
val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
}

You can get System service from Context and Context from View
fun View.showSoftKeyboard() {
if (requestFocus()) {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
}
Now you can extract this method from Fragment

Related

Why my ViewModel is still alive after I replaced current fragment in Android?

Example, If I replaced 'fragmentA' with 'fragmentB', the 'viewModelA' of fragmentA is still live. why ?
onCreate() of Fragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider.NewInstanceFactory().create(InvoicesViewModel::class.java)
}
ViewModel
class InvoicesViewModel : ViewModel() {
init {
getInvoices()
}
private fun getInvoices() {
viewModelScope.launch {
val response = safeApiCall() {
// Call API here
}
while (true) {
delay(1000)
println("Still printing although the fragment of this viewModel destroied")
}
if (response is ResultWrapper.Success) {
// Do work here
}
}
}
}
This method used to replace fragment
fun replaceFragment(activity: Context, fragment: Fragment, TAG: String) {
val myContext = activity as AppCompatActivity
val transaction = myContext.supportFragmentManager.beginTransaction()
transaction.replace(R.id.content_frame, fragment, TAG)
transaction.commitNow()
}
You will note the while loop inside the Coroutine still work although after replace fragment to another fragment.
this is about your implementation of ViewModelProvider.
use this way for creating your viewModel.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(InvoicesViewModel::class.java)
}
in this way you give your fragment as live scope of view model.
Check, if you have created the ViewModel in Activity passing the context of activity or fragment.

Handle navigation in android mvvm

I am building an android application and I am not sure about how to implement navigation in MVVM architecture.
The first approach I took is to bind the on click event button to a function in the ViewModel that does some logic if necessary(for example some data validation) and then fires a LiveData event(that tells the view to navigate to different screen) to a view that observes the ViewModel.
<button android:id="#+id/btnId"
android:onClick="#{(v) -> myViewModel.onSaveClick()}"
.../>
class MyViewModel : ViewModel() {
val saveNavigation = MutableLiveData<Event<Customer>>()
val errorMessage = MutableLiveData<Event<String>>()
fun onSaveClick() {
if (validateCustomer(customer)) {
repository.save(customer)
saveNavigation.value = Event(customer)
}
else
errorMessage.value = Event("error")
}
}
class View : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//observe event protects from re-reading the value on screen rotation
myViewModel.saveNavigation.observeEvent(this) {
findNavController().navigate(CustomerViewDirections
.actionCustomerInfoToCustomerBalanceHistory(it))
}
myViewModel.errorMessage.observeEvent(this) { toast(it) }
}
}
The second approach is that the view registers to an onClickListener and actively calls the ViewModel logic function(for example validation) and only after that the view does the navigation
class MyViewModel : ViewModel() {
fun save() : Status {
if (validateCustomer(customer)) {
repository.save(customer)
return Status.OK
}
else
return Status.Error //or some string message
}
}
class View : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
btnId.setOnClickListener {
if (myViewModel.save() == Status.OK)
findNavController().navigate(CustomerViewDirections
.actionCustomerInfoToCustomerBalanceHistory(myViewModel.customer))
else
toast("error")
}
}
}
Which of these two ways is better for the MVVM architecture or maybe there is another option to go with?
I would suggest you create a Navigator interface that gets injected to your ViewModels using your dependency injection of choice. You then implement that interface in a component that handles the navigation. You would then navigate using the methods from that Navigator interface.
For instance,
interface Navigator {
fun navigateToSettings()
}
class NavigatorImpl : Navigator {
override fun navigateToSettings() {
TODO()
}
}

How to do generic onTouchEvent method for kotlin?

I have such a method. In loginactivity.kt. What I want to do is make this method generic and use it everywhere. I want to write a method in CommonExtensions.kt, but I can't write it right and I get an error. How can I make Generic become
LoginActivity.kt
override fun onTouchEvent(event: MotionEvent?): Boolean {
val imm = this.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(this.currentFocus?.windowToken, 0)
return super.onTouchEvent(event)
}
CommonExtensions.kt
fun Context.onTouchEvent(event: MotionEvent?): Boolean {
val imm = this.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(this.onTouchEvent()?.windowToken, 0)
}
You have a couple of options:
Create a basic class for Activities, for example BaseActivity and override onTouchEvent method there. Inherit from that activity other activities.
Create some util file, for example UiUtils.kt and define method of hiding keyboard there, e.g.:
fun hideKeyboard(view: View) {
val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
Call it from your activities' onTouchEvent() method:
override fun onTouchEvent(event: MotionEvent?): Boolean {
hideKeyboard(someView)
return super.onTouchEvent(event)
}
Create extension function on View in your CommonExtensions.kt file:
fun View.hideKeyboard() {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(windowToken, 0)
}
And call it from onTouchEvent() method:
override fun onTouchEvent(event: MotionEvent?): Boolean {
someView.hideKeyboard()
return super.onTouchEvent(event)
}

Triggering an Interface in Kotlin for Android

There is a specific implementation of interface when it comes to using Kotlin for Android: triggering an interface from a fragment. Consider the common scenario where a Fragment must communicate a UI action to the parent Activity. In Java, we would define the interface, create a "listener" instance of it, and implement/override actions in interface parents. It is the creating a listener instance that is not so straightforward to me. After some googling, I found an implementation example but I do not understand why it works. Is there a better way to do this? Why must it be implemented the way it is below?
class ImplementingFragment : Fragment(){
private lateinit var listener: FragmentEvent
private lateinit var vFab: FloatingActionButton
//Here is the key piece of code which triggers the interface which is befuddling to me
override fun onAttach(context: Context?) {
super.onAttach(context)
if(context is FragmentEvent) {
listener = context
}
}
//Notice the onClickListener using our interface listener member variable
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val v: View? = inflater.inflate(R.layout.fragment_layout, container, false)
vFab = v?.findViewById(R.id.fh_fab)
vFab.setOnClickListener{listener.SomethingHappened()}
return v
}
}
interface FragmentEvent{
fun SomethingHappened()
}
In this code lateinit var listener: FragmentEvent should be initialized and can't be null. So onAttach should looks like
override fun onAttach(context: Context?) {
super.onAttach(context)
if (context is FragmentEvent) {
listener = context
} else {
throw RuntimeException(context!!.toString() + " must implement FragmentEvent")
}
}
In this case if you forget to implement FragmentEvent in Activity you'll got an exception, otherwise callback is ready to use.
you could also use a try catch block and get the classic Java pattern
override fun onAttach(context: Context) {
super.onAttach(context)
try {
listener = context as YourInterface
} catch (e: IllegalStateException) {
Log.d("TAG", "MainActivity must implement YourInterface")
}
}
the 'as' keyword can be used in to replicate java explicit typecasting in kotlin
val customItem = arguments?.getSerializable("KEY") as ArrayList<Custom>

Close/hide the Android Soft Keyboard with Kotlin

I'm trying to write a simple Android app in Kotlin. I have an EditText and a Button in my layout. After writing in the edit field and clicking on the Button, I want to hide the virtual keyboard.
There is a popular question Close/hide the Android Soft Keyboard about doing it in Java, but as far as I understand, there should be an alternative version for Kotlin. How should I do it?
Use the following utility functions within your Activities, Fragments to hide the soft keyboard.
(*)Update for the latest Kotlin version
fun Fragment.hideKeyboard() {
view?.let { activity?.hideKeyboard(it) }
}
fun Activity.hideKeyboard() {
hideKeyboard(currentFocus ?: View(this))
}
fun Context.hideKeyboard(view: View) {
val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
}
This will close the keyboard regardless of your code either in dialog fragment and/or activity etc.
Usage in Activity/Fragment:
hideKeyboard()
I think we can improve Viktor's answer a little. Based on it always being attached to a View, there will be context, and if there is context then there is InputMethodManager:
fun View.hideKeyboard() {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(windowToken, 0)
}
In this case the context automatically means the context of the view.
What do you think?
Simply override this method in your activity. It will automatically works in its child fragments as well.....
In JAVA
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (getCurrentFocus() != null) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}
return super.dispatchTouchEvent(ev);
}
In Kotlin
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
if (currentFocus != null) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(currentFocus!!.windowToken, 0)
}
return super.dispatchTouchEvent(ev)
}
In your Activity or Fragment create a function as:
fun View.hideKeyboard() {
val inputManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputManager.hideSoftInputFromWindow(windowToken, 0)
}
suppose you have a button with an id your_button_id in XML file related to this Activity or Fragment, so, on button click event:
your_button_id.setOnClickListener{
it.hideKeyboard()
}
Peter's solution solves neatly the problem by extending functionality of View class. Alternative approach could be to extend functionality of Activity class and thus bind operation of hiding keyboard with View's container rather than View itself.
fun Activity.hideKeyboard() {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(findViewById(android.R.id.content).getWindowToken(), 0);
}
I didn't see this variant of Kotlin extension function:
fun View.hideSoftInput() {
val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(windowToken, 0)
}
Its benefit is that this extension function could be called from every CustomView and in every click or touch listener
Make an object class named Utils:
object Utils {
fun hideSoftKeyBoard(context: Context, view: View) {
try {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm?.hideSoftInputFromWindow(view.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
} catch (e: Exception) {
// TODO: handle exception
e.printStackTrace()
}
}
}
You can use this method in any class where you want to hide the soft input keyboard. I am using this in my BaseActivity.
Here the view is any view that you use in your layout:
Utils.hideSoftKeyBoard(this#BaseActivity, view )
You can use Anko to make life easier, so the line would be:
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
or maybe better to create extension function:
fun View.hideKeyboard(inputMethodManager: InputMethodManager) {
inputMethodManager.hideSoftInputFromWindow(windowToken, 0)
}
and call it like this:
view?.hideKeyboard(activity.inputMethodManager)
Although there are many answers but this answer is related to a best practice in KOTLIN by opening and closing the keyboard with life cycle and extension function.
1). Create Extension Functions create a file EditTextExtension.kt and paste the below code
fun EditText.showKeyboard(
) {
requestFocus()
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as
InputMethodManager
imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
}
fun EditText.hideKeyboard(
) {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as
InputMethodManager
imm.hideSoftInputFromWindow(this.windowToken, 0)
}
2). Create LifeCycleObserver Class Create a class EditTextKeyboardLifecycleObserver.kt and paste the code below
class EditTextKeyboardLifecycleObserver(
private val editText: WeakReference<EditText>
) :
LifecycleObserver {
#OnLifecycleEvent(
Lifecycle.Event.ON_RESUME
)
fun openKeyboard() {
editText.get()?.postDelayed({ editText.get()?.showKeyboard() }, 50)
}
fun hideKeyboard() {
editText.get()?.postDelayed({ editText.get()?.hideKeyboard() }, 50)
}
}
3). Then use the below code in onViewCreated / onCreateView
lifecycle.addObserver(
EditTextKeyboardLifecycleObserver(
WeakReference(mEditText) //mEditText is the object(EditText)
)
)
The Keyboard will open when the user opens the fragment or activity.
if you occur any problems, following the solution feel free to ask in the comment.
Here is my solution in Kotlin for Fragment. Place it inside setOnClickListener of the button.
val imm = context?.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager?
imm?.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0)
I found the answer that worked for me here: http://programminget.blogspot.com/2017/08/how-to-close-android-soft-keyboard.html
val inputManager:InputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputManager.hideSoftInputFromWindow(currentFocus.windowToken, InputMethodManager.SHOW_FORCED)
This works well with API 26.
val view: View = if (currentFocus == null) View(this) else currentFocus
val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
Write a function to hide the keyboard:
private fun hideKeyboard(){
// since our app extends AppCompatActivity, it has access to context
val imm=getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
// we have to tell hide the keyboard from what. inorder to do is we have to pass window token
// all of our views,like message, name, button have access to same window token. since u have button
imm.hideSoftInputFromWindow(button.windowToken, 0)
// if you are using binding object
// imm.hideSoftInputFromWindow(binding.button.windowToken,0)
}
You have to call this function whereever u need
Thanks to #Zeeshan Ayaz
Here is a little improved version
Because 'currentFocus' is nullable we better check it using Kotlin's ?.let
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
currentFocus?.let { currFocus ->
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(currFocus.windowToken, 0)
}
return super.dispatchTouchEvent(ev)
}
You can use from bellow code, I write bellow code in my fragment:
private val myLayout = ViewTreeObserver.OnGlobalLayoutListener {
yourTextView.isCursorVisible = KeyboardTool.isSoftKeyboardShown(myRelativeLayout.rootView)
}
Then in onViewCreated of fragment:
......
super.onViewCreated(view, savedInstanceState)
myRelativeLayout.viewTreeObserver.addOnGlobalLayoutListener(myLayout)
......
And in onDestroyView use too:
override fun onDestroyView() {
super.onDestroyView()
myRelativeLayout.viewTreeObserver.removeOnGlobalLayoutListener(myLayout)
}
And:
object KeyboardTool {
fun isSoftKeyboardShown(rootView: View): Boolean {
val softKeyboardHeight = 100
val rect = Rect()
rootView.getWindowVisibleDisplayFrame(rect)
val dm = rootView.resources.displayMetrics
val heightDiff = rootView.bottom - rect.bottom
return heightDiff > softKeyboardHeight * dm.density
}
}
Kotlin
I use bellow code:
import splitties.systemservices.inputMethodManager
inputMethodManager.hideSoftInputFromWindow(view?.windowToken, 0)
You can use a Function Extension in Kotlin. Replace activity by fragment if you need make it in Fragment.
fun Activity.hideKeyboard() {
hideKeyboard(currentFocus ?: View(this))
}
Hello I frequently use these two extension functions for showing and hiding soft keyword.
Show Soft Keyboard
fun Any.showSoftKeyboard(view: View, context: Context) {
if (view.requestFocus()) {
val imm: InputMethodManager =
context.getSystemService(Context.INPUT_METHOD_SERVICE) as
InputMethodManager
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
}
}
Hide Soft Keyboard
fun Any.hideSoftKeyboard(view: View, context: Context) {
val imm =
context.getSystemService(Context.INPUT_METHOD_SERVICE) as
InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
You can use these methods in any Object Class to access these globally or you can make separate Extensions/CommonUtils Files for these.

Categories

Resources