Kotlin: LoadURL onReceivedError() not firing - android-studio

Simple kotlin app under android studio that makes a loadURL to a local address:-
The function often fails, probably due to local net latency with:
Web Page not available
The web page at http://192.168.1.144/apikey/webcam could not be loaded because:
net: ERR_ADDRESS_UNREACHABLE
I have
android:usesCleartextTraffic="true"
<uses-permission android:name="android.permission.INTERNET"/>
in the manifest, and the loadurl often is fine
In order to capture the error and provide a message an
onReceivedError()
action is used.
It never fires
Is the syntax of the onReceivedError correct? It refers to WebView rather than my instance myWebview (which causes a reference error), and I've moved the scope around to no effect.
The Android Studio comment says that the function is never used. A big hint, but I can't see which scope to place it in.
Or is this type of error one of those not caught by OnReceivedError. If so, how which function would?
Ideally I'd like to increase the 'wait' time of the LoadUrl function so that the lazy local IP can respond.
I've copied this from other examples.
I'd really welcome some help please
Here is my class code:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create the NotificationChannel
val name = getString(R.string.channel_name)
val descriptionText = getString(R.string.channel_description)
val importance = NotificationManager.IMPORTANCE_DEFAULT
val CHANNEL_ID = "only_channel"
val mChannel = NotificationChannel(CHANNEL_ID, name, importance)
mChannel.description = descriptionText
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(mChannel)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create channel to show notifications.
val channelId = getString(R.string.default_notification_channel_id)
val channelName = getString(R.string.default_notification_channel_name)
val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager?.createNotificationChannel(NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_HIGH))
}
val myWebView: WebView = findViewById(R.id.webview)
/*myWebView.loadUrl("https://amazon.co.uk")*/
myWebView.webViewClient = WebViewClient()
myWebView.setWebViewClient(object : WebViewClient() {
fun onReceivedError(view: WebView , errorCode: Int, description: String, failingUrl: String, getContext: Context) {
Log.i("WEB_VIEW_TEST", "error code:$errorCode")
Toast.makeText(getContext, "Webcam not reachable",Toast.LENGTH_SHORT ).show()
}
})
WebView.setWebContentsDebuggingEnabled(true)
/*5 March 2021*/
myWebView.clearCache(true)
myWebView.loadUrl("http://192.168.1.144/apikey/webcam")
val disable_button: Button = findViewById(R.id.disable)
disable_button.setOnClickListener {
myWebView.loadUrl("http://192.168.1.144/apikey/disable")
}
fun onReceivedError(
view: WebView,
request: WebResourceRequest,
error: WebResourceError
) {
Toast.makeText(this, "Webcam not reachable", Toast.LENGTH_SHORT).show()
}
}
}

Related

can't use a toast message in the recyclerview

I am making recyclerview in kotlin in android studio. I'm trying to set up an event listener that puts a button in the recyclerview and outputs a toast message. Even if this#[activity name] is entered instead of this in context, a toast message is not displayed. What went wrong?
The error code is as follows.
Unresolved reference: #UpdateActivity
class UpdateActivity : AppCompatActivity() {
private val vBinding by lazy {ActivityUpdateBinding.inflate(layoutInflater)}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(vBinding.root)
var recyclerViewAdapter = CustomAdapter()
recyclerViewAdapter.listData = arrayListOf<String>("a", "b", "c")
vBinding.uploadedRecyclerView.adapter = recyclerViewAdapter
vBinding.uploadedRecyclerView.layoutManager = LinearLayoutManager(this)
} // onCreate End
class CustomAdapter:RecyclerView.Adapter<CustomAdapter.Holder>(){
var listData = arrayListOf<String>()
class Holder(val vBinding:UploadedRecyclerBinding):RecyclerView.ViewHolder(vBinding.root){
fun setData(name:String){
vBinding.name.text = name
vBinding.getBtn.setOnClickListener {
**Toast.makeText(this#UpdateActivity, "test", Toast.LENGTH_SHORT).show()**
// ↑A compilation error occurs here.**
}
}
} // Holder end
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val vBinding = UploadedRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return Holder(vBinding)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val name = listData[position]
holder.setData(name)
}
override fun getItemCount(): Int {
return listData.size
}
} // CustomAdapter end
} // UpdateActivity end
you could try put context into constructor of adapter when you create it, and use this context to show a toast. something like this:
class CustomAdapter(
private val mContext: Context
):RecyclerView.Adapter<CustomAdapter.Holder>(){
and in holder class:
Toast.makeText(mContext, "test", Toast.LENGTH_SHORT).show()
finally, when you create adapter in activity:
var recyclerViewAdapter = CustomAdapter(this)
Normally, we'll create adapter class in another file, so use this#UpdateActivity in adapter is bad idea
Instead of using the local context in Toast, always use applicationContext when showing it. So, you should replace that creation of Toast message as,
Toast.makeText(applicationContext, "test", Toast.LENGTH_SHORT).show()

Program crashes whenever it gets string data from an another activity

I was just making a simple program that sends string that's typed in an EditText into a TextView in another activity, I'm a beginner and was following a kotlin tutorial on the internet, the program would crash whenever it gets the string from the 1st activity, The code of the 1st activity looks like this:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.button).setOnClickListener {
val message: String? = findViewById<EditText>(R.id.sameer).text.toString()
val kash = Intent(this, kos::class.java)
intent.putExtra("sameer", message)
startActivity(kash)
}
}
}
and the code for the 2nd activity goes like this:
class kos: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.kos)
val kanye: Bundle? = intent.extras
val shaheer = kanye!!.getString("sameer")
Toast.makeText(this, shaheer , Toast.LENGTH_SHORT)
findViewById<TextView>(R.id.UserisHambola).text = shaheer
}
}
I've tried to check when does the error start to happen, it appears that it starts to happen after declaring variable "shaheer" in the 2nd activity, if I delete that code with all the other codes after it, the program doesn't crash, I don't know why does this crash happen, Thank you for your time <3
Try to replace kanye!!.getString("sameer") with kanye?.getStringExtra("sameer") ?: "no data". This way the program will not crash if no value is passed under "sameer" key and the default "no data" will be stored to val shaheer. If you see no data in the toast, you passed the variable between activities incorrectly.
You do this in MainActivity:
val kash = Intent(this, kos::class.java)
intent.putExtra("sameer", message)
startActivity(kash)
You put the extra with the key "sameer" into the Intent referenced by the variable intent. You probably want to put the extra into the Intent referenced by the variable kash. Try this instead:
val kash = Intent(this, kos::class.java)
kash.putExtra("sameer", message)
startActivity(kash)

AndroidStudio Kotlin connect to bluetooth

I've added <uses-permission android:name="android.permission.BLUETOOTH" /> to my manifest but the error
Missing permission required by BluetoothAdapter.isEnabled: android.permission.BLUETOOTH.
is still there.
Also, in ContextCompat.checkSelfPermission(...) what is the first parameter CONTEXT? The documentation https://developer.android.com/training/permissions/requesting does not say.
And am I correct that I need to disconnect and reconnect bluetooth whenever the app is not being used?
class MainActivity : AppCompatActivity() {
var bt: BluetoothAdapter? = null
var bts: BluetoothSocket? = null
val REQUEST_BLUETOOTH_PERMISSION: Int = 1
val REQUEST_BLUETOOTH_ENABLE: Int = 2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))
{
Toast.makeText(
getApplicationContext(),
"Device does not support Bluetooth therefore this application cannot run.",
Toast.LENGTH_SHORT
).show();
return;
}
bt = BluetoothAdapter.getDefaultAdapter()
if (bt == null) {
// This device does not have Bluetooth.
Toast.makeText(
getApplicationContext(),
"Device does not have a Bluetooth adapter therefore this application cannot run.",
Toast.LENGTH_SHORT
).show();
return;
}
bluetoothConnect();
}
fun bluetoothConnect() {
if (ContextCompat.checkSelfPermission(
CONTEXT, // What is this? It's not explained at https://developer.android.com/training/permissions/requesting
Manifest.permission.BLUETOOTH
) == PackageManager.PERMISSION_GRANTED
) {
if (bt.isEnabled == false) { // Error: Missing permission required by BluetoothAdapter.isEnabled: android.permission.BLUETOOTH.
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_BLUETOOTH_ENABLE)
} else {
val pairedDevices: Set<BluetoothDevice>? = bt.bondedDevices
pairedDevices?.forEach { device ->
val deviceName = device.name
val deviceHardwareAddress = device.address // MAC address
}
}
}
else {
// Request permission. That will call back to onActivityResult which in the case of success will call this method again.
// Ask for permission.
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.BLUETOOTH),
REQUEST_BLUETOOTH_PERMISSION
)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_BLUETOOTH_PERMISSION) {
if (resultCode == RESULT_OK) {
bluetoothConnect();
} else {
Toast.makeText(
getApplicationContext(),
"This application cannot run because it does not have Bluetooth permission.",
Toast.LENGTH_SHORT
).show();
// Do we need to quit? How?
}
}
else if( requestCode == REQUEST_BLUETOOTH_ENABLE)
{
if(resultCode == RESULT_OK)
{
// try again
bluetoothConnect();
}
else {
Toast.makeText(
getApplicationContext(),
"This application cannot run because Bluetooth is not enabled and could not be enabled.",
Toast.LENGTH_SHORT
).show();
// Do we need to quit? How?
}
}
}
override fun onPause() {
super.onPause()
// Release Bluetooth
}
override fun onResume() {
super.onResume()
// Connect Bluetooth
}
override fun onStop() {
super.onStop()
// Release Bluetooth
}
override fun onStart() {
super.onStart()
// Connect Bluetooth
}
}
Edit:
added additional BT check to code,
adding manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rwb.btconnectortest">
<uses-permission android:name="android.permission.BLUETOOTH" />
<!--<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />-->
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/btconnectortestTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Just because comments are not good for this, let me give you a list of things you ought to do before you can act with Bluetooth. (apologies this is in Java because that's what I have right now, but very easy to translate to Kotlin if needed)
I'm doing this for BT LE (low energy) which is the preferred way for.. obvious reasons.
Did you add the permission(s) to the Manifest? You need something like
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-permission android:name="android.permission.BLUETOOTH"/>
Make sure Bluetooth exists and is turned on...
// Does BLE exist?
if(getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){
final BluetoothManager manager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
Now that you have a manager, you need to get the BluetoothAdapter:
BluetoothAdapter bluetoothAdapter = manager.getAdapter();
All this is fine in onCreate, but keep in mind that you have to check if BT is enabled every time the user resumes the activity (For it could have been turned off/disabled/revoked/etc).
Likely in onResume:
// obviously, you need to check that Bt adapter isn't null and all that,
// otherwise you ought to go back and "construct" it again, check permissions, etc.
adapter = getBTAdapter(); // do all the checks in there...
boolean bluetoothEnabled = adapter != null && adapter.isEnabled();
If the BT radio is off (user turning it off), you can programmatically enable it, if you have the corresponding permission (which I think is BT admin or similar, you're gonna have to search on that one, because it's been a while).
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> I believe it was.
Since BT is a radio that needs power, it will take a while (seconds) to turn on and be available. For this you need to "listen" with Yet Another broadcast receiver...
In other words, the activity will fire an intent (startActivityForResult(...)) telling Android to enable BT, you will subscribe to that broadcast to listen to the callback. Once android informs you that BT is on, you can go back to step 1 and start checking if it's enabled, you have permission, etc.
The callback is if I have not forgotten too much... looked like
public void onReceive(Context context, Intent intent) {
In there you ought to check for various BluetoothAdapter states... among them:
BluetoothAdapter.ACTION_STATE_CHANGED
This signals that the state changed, but another nested if is needed to determine to what state...
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE);
switch (state) {
case BluetoothAdapter.STATE_OFF:
case BluetoothAdapter.STATE_TURNING_OFF:
case BluetoothAdapter.STATE_TURNING_ON:
case BluetoothAdapter.STATE_ON:
}
Those are all the ones you care (check the BluetoothAdapter enum for more info).
In the ON you know BT is on... so..
Now you can tell the adapter that you want to scan...
adapter.startLeScan(callback);
(remember to call stopLeScan(callback) when you're done).
As each device is found, the callback will be called with the info you need to attempt to connect and pair (if needed).
The signature of the callback (LeScanCallback) is something like:
public void onScan(final BluetoothDevice device, int rssi, byte[] record);
(I'm typing by memory, so it may be a different name but you get the idea)
This is, as far as I can remember the old API.
API 21 has a ScanSettings.Builder() where you can specify how you want to scan, but it's essentially a similar method. Initiate scan, pass a callback and wait for results to show up.
You have various modes too:
SCAN_MODE_BALANCED: Balance battery efficiency and scan speed
SCAN_MODE_LOW_LATENCY: Prefer scan speed over battery
SCAN_MODE_LOW_POWER: Prefer battery efficiency over scan speed
SCAN_MODE_OPPORTUNISTIC: can't remember :) I think it was to use other scanner results 'around' you. Never used it.
Once you have identified the device you were looking for the BluetoothDevice has everything you need to tell BT to "connect" to it.
public void onScanResult(int callbackType, ScanResult scanResult) {
^ this is the signature of the "new" Scanner.
From that ScanResult, you can do:
int rssi = result.getRssi();
BluetoothDevice device = result.getDevice();
String advertiseName = device.getName();
String macAddress = device.getAddress();
If the scan fails for any reason, you get a callback on onScanFailed(int errorCode).
And again, there are various "reasons" (check the errorCode) why the scan failed.
Remember I may be mixing API 18 or API 21 "apis" here, but the concept is very similar in both.
Once you have finally grabbed a Device's MAC address... you can ask the adapter to try to connect to it:
BluetoothDevice device = adapter.getRemoteDevice(macAddress);
device.connectGatt(context, false, anotherCallback);
The callback is of BluetoothGattCallback and again, it has a bunch of methods among them onConnectionStateChange...
At this point you ought to read more about how Bluetooth works (and how it works on Android) because there are various modes (Gatt being one way) of operating with BT. It's impossible to know each and how/what you want to do once connected.
The rule of thumb will be: make sure you're prepared to having to re-pair or re-request permissions, because it's ultimately the user's choice to disable, turn off, walk-away, revoke permission, etc. at any point during this.
Good luck!
in manifest file add these two permission
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH"
#########################################################
*NOTE:i attached my bluetooth kotlin code , and its work with me. I enter code herehope this helpful
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var myBluetooth:BluetoothAdapter? = null
lateinit var mypairedDevices:Set<BluetoothDevice>
val Request_Enable_Blutooth=1
companion object {
val EXTRA_ADDRESS :String= "Device_Address"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
myBluetooth= BluetoothAdapter.getDefaultAdapter()
if (myBluetooth == null)
{
Toast.makeText(applicationContext, "Bluetooth Device Not Available", Toast.LENGTH_LONG).show()
}
if (!myBluetooth!!.isEnabled)
{
val enableBlutoothIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBlutoothIntent, Request_Enable_Blutooth)
}
binding.BTNPairedDevices.setOnClickListener {
pairedDeviceList()
}
}
private fun pairedDeviceList (){
mypairedDevices = myBluetooth!!.bondedDevices
val list : ArrayList<BluetoothDevice> = ArrayList()
if (!mypairedDevices.isEmpty())
{
for ( device:BluetoothDevice in mypairedDevices)
list.add(device)
//list.add(device.name() + "\n" + device.address())
Log.i("Device", "This is messeage")
}
else {
Toast.makeText(applicationContext, " NO PAIRED DEVICES FOUND", Toast.LENGTH_LONG).show()
}
val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, list)
binding.DeviceListView.adapter = adapter
binding.DeviceListView.onItemClickListener = AdapterView.OnItemClickListener{ _, _, position, _ ->
val device: BluetoothDevice = list[position]
val address: String = device.address
val intent = Intent(this, LedController::class.java)
intent.putExtra(EXTRA_ADDRESS, address)
startActivity(intent)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == Request_Enable_Blutooth)
{
if(resultCode ==Activity.RESULT_OK)
{
if (myBluetooth!!.isEnabled)
{ Toast.makeText(applicationContext, "Bluetooth Enabled", Toast.LENGTH_LONG).show()
}
else ( Toast.makeText(applicationContext, "Bluetooth Disabled", Toast.LENGTH_LONG).show()
)
}
} else if(resultCode == Activity.RESULT_CANCELED)
Toast.makeText(applicationContext, "Bluetooth has been canceled", Toast.LENGTH_LONG).show()
}
}
I restarted AndroidStudio and now the error has disappeared. What a complete piece of rubbish.
But now the layout is broken...

How to Pagination using retrofit to fetch next items from API on Scrolling in kotlin?

I want to make my recyclerview paginationscrolling using retrofit.
I already complete get Json data using retrofit. It means interface API is correct from API Document.
However, if I loaded more 20items. can not scroll more items in Client.
when I checked Server data. per one page can get maximum 20items.
For example, if I loaded 25items in my recyclerview.
page 0: 20, page 1: 5.
if I want scrolling all items, How can I make paginationscrolling for retrofit??
check some my code and help me..
Response
Interface API
#GET("/store/cart/mine")
fun getCart(#Header("Authorization") token: String?, #Query("page") page:Int): Call<CartResponse>
CartViewActivity
class CartViewActivity : AppCompatActivity(), SwipeRefreshLayout.OnRefreshListener {
private val lastVisibleItemPosition: Int
get()= LinearLayoutManager.HORIZONTAL
private lateinit var scrollListener: RecyclerView.OnScrollListener
lateinit var mAdapter: CartItemRecyclerAdapter
#RequiresApi(Build.VERSION_CODES.N)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cart_view)
val page = 0
val token = SharedPreference.getTokenInfo(this)
Client.retrofitService.getCart(token,page).enqueue(object :Callback<CartResponse> {
override fun onResponse(call: Call<CartResponse>, response: Response<CartResponse>) {
if (response?.isSuccessful == true) {
swipeRefreshLo.setOnRefreshListener(this#CartViewActivity)
showdata(response.body()?.docs!!)
}else if(response?.isSuccessful==false) {
val er = Gson().fromJson(response.errorBody()?.charStream(), ErrorResponse::class.java)
if (er.code==60202) {
}
}
}
override fun onFailure(call: Call<CartResponse>, t: Throwable) {
}
})
}
private fun showdata(results: MutableList<cartDocs>) {
recycler_view.apply {
mAdapter=CartItemRecyclerAdapter(context,context as Activity, results)
recycler_view.adapter=mAdapter
recycler_view.layoutManager=LinearLayoutManager(context)
setRecyclerViewScrollListener()
}
}
private fun setRecyclerViewScrollListener() {
scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
val totalItemCount = recyclerView.layoutManager!!.itemCount
if(totalItemCount == lastVisibleItemPosition + 1) {
Log.d("MyTAG", "Load new list")
recyclerView.removeOnScrollListener(scrollListener)
}
}
}
}
override fun onRefresh() {
swipeRefreshLo.isRefreshing = false
}
}
Firstly, you need to modify your adapter to be able to update existing data, instead of creating new adapter every time you fetch new data.
Adapter should be initialised before getting the data, to be able to call methods on it.
You should create a method inside the adapter, something like
fun updateData(results: MutableList<cartDocs>) {
dataSet.addAll(results)
notifyItemRangeInserted(start, newItemsSize)
}
Then, we you get response from server in onSuccess() you should call method above, and increment page, so the next time you load data, you get new items.
Data should be fetched first time when screen loads (page will be 0), and then when user scrolls to bottom of RV ().

Can't communication between DialogFragment and Activity using Observer pattern?

When you press the button to open a separate input window, there is a function to display the results toast.
class MainActivity : AppCompatActivity() {
val disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
val f = TestPopup()
usingRxJava(f)
//usingLiveData(f)
}
}
private fun usingRxJava(f: TestPopup) {
val subject = SingleSubject.create<String>()
f.show(supportFragmentManager, "TAG")
button.post {
f.dialog.setOnDismissListener {
val str = f.arguments?.getString(TestPopup.TEST_KEY) ?: ""
subject.onSuccess(str)
}
}
subject.subscribe({
Toast.makeText(this, "Accept : $it", Toast.LENGTH_SHORT).show()
}, {
}).addTo(disposable)
}
private fun usingLiveData(f: TestPopup) {
val liveData = MutableLiveData<String>()
f.show(supportFragmentManager, "TAG")
button.post {
f.dialog.setOnDismissListener {
val str = f.arguments?.getString(TestPopup.TEST_KEY) ?: ""
liveData.postValue(str)
}
}
liveData.observe(this, Observer {
Toast.makeText(this, "Accept : $it", Toast.LENGTH_SHORT).show()
})
}
override fun onDestroy() {
disposable.dispose()
super.onDestroy()
}
}
DialogFragment
class TestPopup : DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.dialog_test, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
button_test.setOnClickListener {
val arg = Bundle()
arg.putString(TEST_KEY, edit_test.text.toString())
arguments = arg
dismiss()
}
}
companion object {
const val TEST_KEY = "KEY"
}
}
(Sample Project Url : https://github.com/heukhyeon/DialogObserverPattern )
This sample code works in normal cases. However, the toast does not float after the following procedure.
Developer Option - Dont'keep activities enable
Open TestPopup, and enter your text. (Do not press OK button)
Press the home button to move the app to the background
The app is killed by the system.
Reactivate the app (Like clicking on an app in the apps list)
In this case, the text I entered remains on the screen, but nothing happens when I press the OK button.
Of course I know this happens because at the end of the activity the observe relationship between the activity and the Dialog is over.
Most of the code uses the implementation of the callback interface for that Dialog in the Activity to handle that case.
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
button_test.setOnClickListener {
val input = edit_test.text.toString()
(activity as MyListener).inputComplete(input)
dismiss()
}
}
class MainActivity : AppCompatActivity(), TestPopup.MyListener {
override fun inputComplete(input: String) {
Toast.makeText(this, "Accept : $input", Toast.LENGTH_SHORT).show()
}
}
But I think it's a way that doesn't match the Observer pattern, and I want to implement it using the Observer pattern as much as possible.
I'm thinking of getting a Fragment from the FragmentManager and subscribing again at onCreate, but I think there's a better way.
Can someone help me?
Your understanding of the problem is correct, except that the problem happens with any configuration changes, including screen rotation. You can reproduce issue without using the developer mode. Try this for example:
Open TestPopup, and enter your text. (Do not press OK button)
Rotate screen
See toast message not popping up.
Also note that your "observer pattern" implementation is not a proper observer pattern. Observer pattern has a subject and an observer. In your implementation, the activity is acting as both the subject and the observer. The dialog is not taking any part in this observer pattern, and using .setOnDismissListener is just another form of a listener pattern.
In order to implement observer pattern between the Fragment(the subject) and the Activity(the observer), the Activity needs to get the reference of the Fragment using the FragmentManager as you suggested. I suggest to use view model and establish observer pattern between view layer and view model layer instead.
RxJava example:
//MainViewModel.kt
class MainViewModel: ViewModel() {
val dialogText = PublishProcessor.create<String>()
fun postNewDialogText(text: String) {
dialogText.onNext(text)
}
}
// Activity
val disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
viewModel.dialogText.subscribe {
Toast.makeText(this, "Accept : $it", Toast.LENGTH_SHORT).show()
}.addTo(disposable)
button.setOnClickListener {
TestPopup().show(supportFragmentManager, "TAG")
// usingRxJava(f)
// usingLiveData(f)
}
}
override fun onDestroy() {
disposable.dispose()
super.onDestroy()
}
// Dialog Fragment
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// Important!! use activity when getting the viewmodel.
val viewModel = ViewModelProviders.of(requireActivity()).get(MainViewModel::class.java)
button_test.setOnClickListener {
viewModel.postNewDialogText(edit_test.text.toString())
dismiss()
}
}

Resources