Passing value through Interface - android

I am new to kotlin and i am trying to pass a value of checked radio button from one class to another activity through interface. I have an interface named RadioGroupHelperInterface as
interface RadioGroupHelperInterface {
fun onSelect(selectedItem: String)
}
Then i have a class from where i want to pass the value of checked radio button.
class GRadioGroupHelper {
private val radioGroupHelperInterface: RadioGroupHelperInterface? = null
fun setRadioExclusiveClick(parent: ViewGroup?) {
val radios: List<RadioButton>? = parent?.let { getRadioButtons(it) }
if (radios != null) {
for (radio in radios) {
radio.setOnClickListener { v ->
val r: RadioButton = v as RadioButton
r.isChecked = true
radioGroupHelperInterface?.onSelect(r.text as String)
checkedValue = r.text as String
for (r2 in radios) {
if (r2.getId() !== r.getId()) {
r2.isChecked = false
}
}
}
}
}
}
}
Finally my activity is as follows:
class ChooseCategoryActivity : AppCompatActivity(), View.OnClickListener,RadioGroupHelperInterface {
var radioGRadioGroupHelper=GRadioGroupHelper()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_choose_category)
setListener()
val parent: ViewGroup = findViewById(R.id.svCategories)
radioGRadioGroupHelper.setRadioExclusiveClick(parent)
}
override fun onSelect(selectedItem: String) {
Log.e("Here","reached")
Log.e("value",selectedItem)
Toast.makeText(this,selectedItem,Toast.LENGTH_LONG).show()
}
}
But i am not able to get the value that i have checked in the radio box from the activity though the value can be printed in the RadioGRadioGroupHelper class. Can anybody please help me?

You don't set radioGroupHelpedInterface field to any value except for null, which is its initial state. Why don't you try this:
Declare your GRadioGroupHelper as following:
class GRadioGroupHelper (private val helperInterface: RadioGroupHelperInterface) {
// All your logic remains the same
}
This will allow you to avoid nullability of the RadioGroupHelperInterface instance and you will also be able to set it via constructor like this in the activity:
val radioGRadioGroupHelper = GRadioGroupHelper(this)
Note that I changed var to val as we don't expect your radioGRadioGroupHelper to change.

Related

Trying to update already existing SharedPreferences values

I am trying to save a name in SharedPreferences. The app is set up so that it recognizes devices by their MAC address. There's an EditText view that populates with the advertised name of the BLE peripheral, and then the user can alter the name inside of the EditText. Once the EditText loses focus, the Data class is supposed to save the user input in SharedPreferences, where the address of the device is the key and the name of the device is the value. Instead, when the EditText loses focus, the name just reverts to the original name found in the BLE advertisement.
I have already looked for answers from these resources (1, 2, 3, 4, 5).
As far as I can tell, I have everything set up correctly in my application, but I am not getting the results I'm looking for. I have three main classes: MainActivity, ScanResultAdapter, and Data. MainActivity is the driver of the program, ScanResultAdapter manages the incoming BLE advertisements from peripheral devices, and Data is a singleton class that manages information for the entire application.
This is how I have it set up in my application:
MainActivity
...
// declaration of the myPrefs variable
lateinit var myPrefs: SharedPreferences
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// instantiation of the myPrefs variable
myPrefs = PreferenceManager.getDefaultSharedPreferences(this)
// passing the context to the data-tracking Data class so that it can access MainActivity's SharedPreferences
data = Data.getInstance(this)
mainBinding.saveNamesButton.onClick {
mainBinding.saveNamesButton.requestFocusFromTouch()
}
rowScanResultBinding.deviceName.onFocusChangeListener = OnFocusChangeListener { v, hasFocus ->
if (!hasFocus) {
val name = rowScanResultBinding.deviceName.text.toString()
val address = rowScanResultBinding.macAddress.text.toString()
if (data.sharedPref.contains(address) && rowScanResultBinding.deviceName.text.toString() != data.sharedPref.getString(address, null)) {
rowScanResultBinding.deviceName.setText(data.sharedPref.getString(address, null))
}
}
}
}
...
override fun onPause() {
super.onPause()
// TODO: Fix this line to resolve an initialization error
data.saveSharedPreference()
}
ScanResultAdapter
...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (items[position].device.name == "EES-TEMP-1") {
val item = items[position]
if (!data.names.contains(item.device.address)) {
data.names[item.device.address] = item.device.name
data.sharedPref.edit().putString(item.device.address, item.device.name).apply()
}
println(data.names)
holder.bind(item)
} else return
}
...
inner class ViewHolder(
private val view: View,
private val onClickListener: ((device: ScanResult) -> Unit)
) : RecyclerView.ViewHolder(view) {
...
with(view) {
if (data.hasName(result.device.address) && !device_name.hasFocus()) {
device_name.setText(data.getName(result.device.address))
} else if (!data.hasName(result.device.address) && !device_name.hasFocus()) {
device_name.setText(result.device.address)
}
}
Data
class Data(private val context: Context) {
companion object {
private var instance: Data? = null
fun getInstance(context: Context): Data {
return instance ?: synchronized(this) {
instance ?: Data(context.applicationContext).also { instance = it }
}
}
}
val names: MutableMap<String, String> = mutableMapOf()
val sharedPref: SharedPreferences = context.getSharedPreferences("MySharedPreferencesFile", Context.MODE_PRIVATE)
val addresses = mutableSetOf<String>()
fun saveSharedPreference() {
sharedPref.edit {
for ((key, value) in names) {
if (!sharedPref.contains(key)) {
putString(key, value)
apply()
}
}
}
}
fun getName(address: String): String? {
if (sharedPref.contains(address)) {
return sharedPref.getString(address, "Unnamed")
}
return "failed"
}
fun hasName(key: String): Boolean {
return sharedPref.contains(key)
}
fun addName(key: String, value: String) {
names[key] = value
}
fun addAddress(address: String) {
addresses.add(address)
}
fun addItem(address: String, name: String) {
sharedPref.edit().putString(address, name).apply()
}
}

What is the parent class of a generated ViewBinding?

I'm trying to DRY up my code and I have a couple activities which use the same blocks of code which I want to move into a method in the parent activity. The problem is that this code uses generated ViewBindings which are unique classes, and I can't figure out what the parent class is in order to use it as a method parameter.
For example, this code is in two different activities and the only difference is that in one activity binding = Activity1Binding, in the other one it's Activity2Binding. They share some views with the same IDs.
binding.noteTitleTV.setOnClickListener { changeTitle() }
binding.deleteModalLayout.setOnClickListener { binding.deleteModalLayout.visibility = View.GONE }
binding.cancelDeleteButton.setOnClickListener { binding.deleteModalLayout.visibility = View.GONE }
binding.confirmDeleteButton.setOnClickListener { onDeleteNoteClicked() }
I would like to implement something like this in the parent activity to prevent duplicate code, if that's possible:
fun setUp(binding: [BINDING PARENT CLASS]) {
binding.noteTitleTV.setOnClickListener { changeTitle() }
// etc
}
The generated classes extend the Object class (java.lang.Object)
The binding class inherits from ViewDataBinding, so you could do this (Kotlin code)
fun setUp(binding: ViewDataBinding) {
when(binding){
is Activity1Binding -> { (binding as Activity1Binding).noteTitelTV.setOnClickListner{ changeTitle() } }
is Activity2Binding -> { (binding as Activity2Binding).noteTitelTV.setOnClickListner{ changeTitle() } }
}
// etc
}
I don't know that it could get more "generic" than that as you don't control the generated classes. But that would at least allow you to place all the code in a single class as you suggested. I use a similar approach in that I have an lateinit instance of all of my generated binding classes and just set which is active based on the passed variable and use that instance name so i don't have to keep typing as.
ex:
private lateinit var itemBinding : GroceryItemBinding
private lateinit var maintItemBinding : GroceryItemMaintBinding
private lateinit var compareItemBinding : GroceryItemCompareBinding
private lateinit var priceItemBinding : GroceryItemPriceBinding
private lateinit var adItemBinding : GroceryItemAdBinding
when(viewBinding){
is GroceryItemMaintBinding -> {
maintItemBinding = viewBinding as GroceryItemMaintBinding
maintItemBinding.groceryItem = gi
maintItemBinding.groceryItemImage.setOnClickListener { ... }
......
}
is GroceryItemBinding -> {
itemBinding = viewBinding as GroceryItemBinding
}
......
}
ViewBinding can create by bind(view), so you can create a base class like this:
abstract class BaseActivity : AppCompatActivity() {
private lateinit var binding: Activity1Binding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val view = createContentView()
// create ViewBinding
binding = Activity1Binding.bind(view)
}
// create view by subclass
abstract fun createContentView(): View
fun setTextViewTitle(text: CharSequence) {
binding.tvTitle.text = text
}
}
this is the content of Activity1Binding#bind():
#NonNull
public static ActivityMainBinding bind(#NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
int id;
missingId: {
id = R.id.layout;
FinanceLabelLayout layout = rootView.findViewById(id);
if (layout == null) {
break missingId;
}
return new ActivityMainBinding((ConstraintLayout) rootView, layout);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
But this is not recommended.
This is not type safe.

on kotlin, textview not initialized outside onCreate method

I am new at Kotlin and trying to implement MVP Architecture,
Currently I am having problem initializing/setting textview's value outside onCreate() method
here is my code
SplashActivity.kt
class SplashActivity : AppCompatActivity(), Splash.ViewInterface {
lateinit var appDetail: AppDetail
lateinit var textTitle: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textTitle = findViewById(R.id.splash_txt_title) as TextView
AppSingleton.appContext = applicationContext
var splashPresentation = SplashPresentation(this)
splashPresentation.getAppDetailFromService()
}
override fun fetchAppDetailSuccessful(response: SplashServiceObject) {
AppSingleton.initializeAppDetal(Gson().fromJson(response.json_string, AppDetail::class.java))
this.appDetail = AppSingleton.appDetail
}
override fun fetchAppDetailFailed(errMsg: String) {
textTitle.text = errMsg
}
}
SplashPresenter.kt
class SplashPresentation(private val view: Splash.ViewInterface) : Splash.PresentationInterface {
fun getAppDetailFromService() {
var splashService = SplashService()
splashService.getAppDetailFromAssets(this)
}
override fun fetchAppDetailFromServiceSuccessful(response: SplashServiceObject) {
view.fetchAppDetailSuccessful(response)
}
override fun fetchAppDetailFromServiceFailed(errMsg: String) {
view.fetchAppDetailFailed(errMsg)
}
}
SplashService.kt
class SplashService {
fun getAppDetailFromAssets(splashPresentation: SplashPresentation) {
val json_filename = "appdetail.json"
var jsonResponse: JsonResponse = AppSingleton.commonUtils.fetchJsonFromAssets(json_filename, AppSingleton.appContext!!)
if (jsonResponse.json_status) {
var splashServiceObj = SplashServiceObject
splashServiceObj.json_string = jsonResponse.json_info
splashServiceObj.response_msg = "JSON Successful fetched."
splashPresentation.fetchAppDetailFromServiceSuccessful(splashServiceObj)
} else {
splashPresentation.fetchAppDetailFromServiceFailed(jsonResponse.json_info)
}
}
}
in my SplashActivity().onCreate(), I am calling a Presenter that access Service, then the Service return a value to Presenter,
Then Presenter, return value to my SplashActivity's View, one of the function is, fetchAppDetailFailed(errMsg)
when I run the app, it crashes, saying the "textaa" is not yet initialized.
back in Java exp, when the variable is already instantiated on onCreate(), you can call this variable anywhere within the activity.
Thanks in advance!
You cannot instantiate Activities on Android. They are instantiated by the OS, and the OS calls the lifecycle methods on it.
In an MVP pattern, the View and Presenter both reference each other. Since Activity (the View) is the entry point of the application, your Activity should instantiate the Presenter and pass a reference of itself to the Presenter so communication can go both ways.
Also, the reference to the activity in the Presenter should be specified as a ViewInterface, not an Activity, or you're kind of defeating the purpose of using MVP.
class SplashPresentation(private val view: Splash.ViewInterface) : Splash.PresentationInterface {
//... methods that call functions on view
}
class SplashActivity : AppCompatActivity(), Splash.ViewInterface {
private val presenter = SplashPresentation(this)
//...
}

Smart cast to 'Int' is impossible, because 'mViewModel.counter.value' is a complex expression

Here is main activity
class MainActivity : AppCompatActivity() {
private lateinit var mViewModel: MainActivityVm
private lateinit var mTv: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.toolbar))
mTv = findViewById(R.id.tv)
mViewModel =
ViewModelProviders.of(this)
.get(MainActivityVm::class.java)
mViewModel.counter.observe(this, Observer<Int> { counter ->
mTv.setText(counter.toString())
})
fab.setOnClickListener { view ->
if(null != mViewModel.counter.value)
{
mViewModel.counter.value++
}
}
}
// ...
}
I try to increment counter in data model when click on fab button
fab.setOnClickListener { view ->
if(null != mViewModel.counter.value)
{
mViewModel.counter.value++
}
}
but I gett error at line mViewModel.counter.value++
Smart cast to 'Int' is impossible, because 'mViewModel.counter.value' is a complex expression
What does error mean?
Here is data model
class MainActivityVm : ViewModel() {
val counter = MutableLiveData<Int>().apply { postValue(0)}
}
edit
If I comment out null check
fab.setOnClickListener { view ->
//if(null != mViewModel.counter.value)
//{
mViewModel.counter.value++
//}
}
I get error
Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?
edit
using null check with !!
fab.setOnClickListener { view ->
if(null != mViewModel.counter.value)
{
mViewModel.counter!!.value++
}
}
gives initial error
Smart cast to 'Int' is impossible, because 'mViewModel.counter.value' is a complex expression
You just need to use the MutableLiveData#postValue() method.
Also, it's better to put the functionality inside the ViewModel.
Add a method in the ViewModel to increment the counter:
class MainActivityViewModel : ViewModel() {
val counter = MutableLiveData<Int>().apply { postValue(0)}
public fun incrementCounter() {
counter.let {
val currentVal: Int? = it.value
currentVal?.let { cur ->
it.postValue(cur + 1)
}
}
}
}
This can also be written as:
public fun incrementCounter() {
val currentVal: Int? = counter.value
if (currentVal != null)
counter.postValue(currentVal + 1)
}
Then, just call that method when the FAB is clicked:
fab.setOnClickListener { view ->
viewModel.incrementCounter()
}

how to call mainActivity method in customeAdapter

I am using recyclerview in kotlin and I am new to kotlin. I have used button.setOnClickListner method inside this. I want to call a method which is in my mainActivity. How should I do it
I want to call below method which is in mainActivity
fun sendOrder() {
Log.e("TAG", "SendOrder: " )
}
my adapter is below
class CustomAdapterJob(val jobList: ArrayList<JobData>): RecyclerView.Adapter<CustomAdapterJob.ViewHolder>(){
override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
val jobData :JobData = jobList[position]
holder?.textViewId?.text = jobData.id
holder?.textViewArea?.text = jobData.area
holder?.textViewCarType?.text = jobData.carType
holder?.textViewCarName?.text = jobData.carName
holder?. textViewDutyHours?.text = jobData.dutyHours
holder?.textViewWeeklyOff?.text = jobData.weeklyOff
holder?.textViewDriverAge?.text = jobData.driverAge
holder?.textViewDriverExperience?.text = jobData.drivingExperience
holder?.textViewOutstationDays?.text = jobData.outstationDays
holder?.textViewDutyDetails?.text = jobData.dutyDetails
holder?.button?.text =jobData.submit
if(jobData.submit == "true"){
holder?.button?.setVisibility(View.GONE);
}
holder?.button?.setOnClickListener( View.OnClickListener (){
Log.d("TAG", "job list position : ${jobList[position].id}")
var id = jobList[position].id
val p = Pattern.compile("-?\\d+")
val m = p.matcher(id)
while (m.find()) {
System.out.println(m.group())
sendOrder()
}
});
//To change body of created functions use File | Settings | File Templates.
}
override fun getItemCount(): Int {
return jobList.size//To change body of created functions use File | Settings | File Templates.
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
val v=LayoutInflater.from(parent?.context).inflate(R.layout.job_card,parent,false)
return ViewHolder(v)
//To change body of created functions use File | Settings | File Templates.
}
class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
val textViewId = itemView.findViewById<TextView>(R.id.job_id)
val textViewArea = itemView.findViewById<TextView>(R.id.area)
val textViewCarType = itemView.findViewById<TextView>(R.id.car_type)
val textViewCarName = itemView.findViewById<TextView>(R.id.car_name)
val textViewDutyHours = itemView.findViewById<TextView>(R.id.duty_hours)
val textViewWeeklyOff = itemView.findViewById<TextView>(R.id.weekly_off)
val textViewDriverAge = itemView.findViewById<TextView>(R.id.driver_age)
val textViewDriverExperience = itemView.findViewById<TextView>(R.id.driving_experience)
val textViewOutstationDays = itemView.findViewById<TextView>(R.id.outstation_days)
val textViewDutyDetails = itemView.findViewById<TextView>(R.id.duty_details)
val button = itemView.findViewById<Button>(R.id.apply_job)
}}
now how i have to call sendOrder() method in kotline
Its better you create a listener and pass it to the adapter.
Interface
interface ActivityInteractor {
fun onInteraction(data: Any?)
}
Implement the interface in your activity
class MainActivity : Activity(), ActivityInteractor {
override fun onCreate(savedInstance : Bundle) {
CustomAdapterJob(jobList, this)
}
override fun onInteraction(data: Any?) {
// you can do any activity related tasks here
sendOrder()
}
}
Accept the listener in your adapter
class CustomAdapterJob(val jobList: ArrayList<JobData>, val activityInteractor: ActivityInteractor) : RecyclerView.Adapter<CustomAdapterJob.ViewHolder>() {
holder?.button?.setOnClickListener( View.OnClickListener () {
Log.d("TAG", "job list position : ${jobList[position].id}")
var id = jobList[position].id
val p = Pattern.compile("-?\\d+")
val m = p.matcher(id)
while (m.find()) {
System.out.println(m.group())
//sendOrder()
activityInteractor.onInteraction(jobList[position].id)
}
});
}
Instead of creating the new interface you can implement onClickListener in the activity and can pass it as a parameter to the adapter class. In the adapter, you can set this onClick listener to your button.
Use kotlin data binding concept to avoid those boilerplate codes like findViewById. please check this link
First you need to create a context:
private val context: Context
Then add this context, along with other variables you might have, to your adapter constructor:
class Adapter(..., context: Context)
Inside you while loop:
while (m.find()) {
System.out.println(m.group)
if (context is MainActivity)
{
(context as MainActivity).SendOrder()
}
}
Apologies for any syntax error, etc. My Kotlin is still a little rough.
The easiest solution would be to your activity as parameter to your recycler view. Then you could easaly call that function. But obviously this is not a very good aproach, so you should prefer the following.
Create an interface which is implemented by your activity and called instead of the activities method. Within the implementation of the interface function you can call the activity function or whatever you like. As it is implemented by the activity itself you have full access to the whole activity context.
A short example how this could be implemented is already answered here
you can do like this:
holder?.button?.setOnClickListener( View.OnClickListener (){
Log.d("TAG", "job list position : ${jobList[position].id}")
var id = jobList[position].id
val p = Pattern.compile("-?\\d+")
val m = p.matcher(id)
while (m.find()) {
System.out.println(m.group())
mySendOrder()
}
});
public void mySendOrder(){
}
and then in main activity:
yourCustomAdapter = new YourCustomAdapter(){
public void mySendOrder(){
sendOrder();
}
}
In case if you don't need Context or Activity object in your adapter. You can pass callback as parameters. May be something like this
class MyAdapter(private val sendOrder: () -> Unit = {}) : BaseAdapter() {
fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
sendOrder()
}
}
Then implement callback in Activity
fun onCreate(...) {
recyclerView.adapter = MyAdapter() {
// Do something
}
}

Categories

Resources