Get Context of a Service - token

I'm trying to get the context of my Service in order. The service opens up an overlay that draws on other apps. The overlay comes up but if I interact with any of the views, the app crashes and gives this error.
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
Here is the full error.
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:1068)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:409)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:109)
at android.app.Dialog.show(Dialog.java:340)
at android.widget.Spinner$DialogPopup.show(Spinner.java:1146)
at android.widget.Spinner.performClick(Spinner.java:792)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28305)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
From what I've been able to determine through Google Search and SO is that the issue is with the context. Below is the code for my Service.
class Dooa: Service() {
private lateinit var floatView: ViewGroup
private lateinit var floatWindowLayoutParams: WindowManager.LayoutParams
private var LAYOUT_TYPE: Int? = null
private lateinit var windowManager: WindowManager
private lateinit var spinnerAccount: Spinner
private lateinit var tvDateAT: TextView
private lateinit var spinnerType: Spinner
private lateinit var etTitle: EditText
private lateinit var etMemo: EditText
private lateinit var spinnerCategory: Spinner
private lateinit var spinnerDebitOrCredit: Spinner
private lateinit var etAmount: EditText
private lateinit var ibSave: ImageButton
private lateinit var ibCancel: ImageButton
private var account: String = "Joint"
private var debitOrCredit: String = "Debit"
private var category: String = ""
private var type: String = "CC"
private var mils: Long = 0
private var balance: String = ""
private var context: Context? = null
private lateinit var db : FirebaseFirestore
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
context = MyApp().getContext()
Log.d("blocks", "context: $context")
if (intent != null) {
if (intent.action == START) {
db = FirebaseFirestore.getInstance()
val metrics = applicationContext.resources.displayMetrics
val width = metrics.widthPixels
val height = metrics.heightPixels
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
val inflator = this.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
floatView = inflator.inflate(R.layout.dooa_transaction_card, null) as ViewGroup
spinnerAccount = floatView.findViewById(R.id.spinnerAccount)
tvDateAT = floatView.findViewById(R.id.tvDateAT)
spinnerType = floatView.findViewById(R.id.spinnerType)
etTitle = floatView.findViewById(R.id.etTitle)
etMemo = floatView.findViewById(R.id.etMemo)
spinnerCategory = floatView.findViewById(R.id.spinnerCategory)
spinnerDebitOrCredit = floatView.findViewById(R.id.spinnerDebitOrCredit)
etAmount = floatView.findViewById(R.id.etAmount)
ibSave = floatView.findViewById(R.id.ibSave)
ibCancel = floatView.findViewById(R.id.ibCancel)
//ACCOUNT SPINNER
ArrayAdapter.createFromResource(
this,
R.array.accounts,
R.layout.spinner_item
).also { adapter ->
Log.d("blocks", "AS ArrayAdapter ran")
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item)
spinnerAccount.adapter = adapter
spinnerAccount.setSelection(0)
}
spinnerAccount.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selection = parent?.getItemAtPosition(position)
account = selection.toString()
getDB()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
tvDateAT.setOnClickListener {
val c = Calendar.getInstance()
val year = c.get(Calendar.YEAR)
val month = c.get(Calendar.MONTH)
val day = c.get(Calendar.DAY_OF_MONTH)
val dpd = DatePickerDialog(
this#Dooa,
{ view, year, monthOfYear, dayOfMonth ->
// Save milliseconds for date picked.
mils = c.timeInMillis
val m = monthOfYear + 1
// Display Selected date in textbox
tvDateAT.text = getString(
R.string.date_picked,
m.toString(),
dayOfMonth.toString(),
year.toString()
)
},
year,
month,
day
)
dpd.show()
}
//TYPE
ArrayAdapter.createFromResource(
this,
R.array.type,
R.layout.spinner_item
).also { adapter ->
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item)
spinnerType.adapter = adapter
spinnerType.setSelection(0)
}
spinnerType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selection = parent?.getItemAtPosition(position)
type = selection.toString()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
etTitle.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
etTitle.isCursorVisible = true
val updatedFloatParamsFlag = floatWindowLayoutParams
updatedFloatParamsFlag.flags =
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
updatedFloatParamsFlag.flags = WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
windowManager.updateViewLayout(floatView, updatedFloatParamsFlag)
return false
}
})
//CATEGORY
ArrayAdapter.createFromResource(
this,
R.array.category,
R.layout.spinner_item
).also { adapter ->
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item)
spinnerCategory.adapter = adapter
spinnerCategory.setSelection(0)
}
spinnerCategory.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selection = parent?.getItemAtPosition(position)
category = selection.toString()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
//DEBIT OR CREDIT
ArrayAdapter.createFromResource(
this,
R.array.debit_or_credit,
R.layout.spinner_item
).also { adapter ->
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item)
spinnerDebitOrCredit.adapter = adapter
spinnerDebitOrCredit.setSelection(0)
}
spinnerDebitOrCredit.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selection = parent?.getItemAtPosition(position)
debitOrCredit = selection.toString()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
ibSave.setOnClickListener {
// save all the info.
val date = tvDateAT.text
val memo = etMemo.text
val title = etTitle.text
val amount = etAmount.text
val uid = FirebaseAuth.getInstance().currentUser!!.uid
val serverTS = FieldValue.serverTimestamp()
val collection = account
var nb = 0.0
val newBalance = if (debitOrCredit.contains("Debit")){
nb = (balance.toDouble() - etAmount.text.toString().toDouble())
} else {
nb = (balance.toDouble() + etAmount.text.toString().toDouble())
}
val info = hashMapOf(
"date" to date.toString(),
"type" to type,
"title" to title.toString(),
"memo" to memo.toString(),
"category" to category,
"debitOrCredit" to debitOrCredit,
"amount" to amount.toString(),
"clearReconcile" to "NA",
"mils" to mils,
"timeStamp" to serverTS
)
if (date != "Date"){
if (title?.isNotEmpty() == true && amount?.isNotEmpty() == true){
val dbAccountTransaction = db.collection("Users").document(uid).collection(collection)
dbAccountTransaction.add(info)
.addOnSuccessListener {
db.collection("Users").document(uid).collection(collection).document("balance")
.update("balance", nb.toString())
.addOnSuccessListener {
Toast.makeText(this, "Transaction was saved.", Toast.LENGTH_SHORT).show()
val i = Intent(this, MainActivity::class.java)
startActivity(i)
}
}
.addOnFailureListener{
Toast.makeText(this, "There was an error. Transaction wasn't saved.", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "Please fill out Title and Amount.", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "Please select a date.", Toast.LENGTH_SHORT).show()
}
}
ibCancel.setOnClickListener {
stopSelf()
windowManager.removeView(floatView)
}
LAYOUT_TYPE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_TOAST
}
floatWindowLayoutParams = WindowManager.LayoutParams(
(width * 0.55f).toInt(),
(height * 0.55f).toInt(),
LAYOUT_TYPE!!,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
)
floatWindowLayoutParams.gravity = Gravity.CENTER
floatWindowLayoutParams.x = 0
floatWindowLayoutParams.y = 0
windowManager.addView(floatView, floatWindowLayoutParams)
floatView.setOnTouchListener(object : View.OnTouchListener{
val updatedFloatWindowLayoutParam = floatWindowLayoutParams
private var initialX = 0.0
private var initialY = 0.0
private var initialTouchX = 0.0
private var initialTouchY = 0.0
override fun onTouch(v: View?, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
initialX = updatedFloatWindowLayoutParam.x.toDouble()
initialY = updatedFloatWindowLayoutParam.y.toDouble()
initialTouchX = event.rawX.toDouble()
initialTouchY = event.rawY.toDouble()
return true
}
MotionEvent.ACTION_MOVE -> {
updatedFloatWindowLayoutParam.x = (initialX + event.rawX - initialTouchX).toInt()
updatedFloatWindowLayoutParam.y = (initialY + event.rawY - initialTouchY).toInt()
windowManager.updateViewLayout(floatView, updatedFloatWindowLayoutParam)
return true
}
}
return false
}
})
spinnerAccount.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
val updatedFloatParamsFlag = floatWindowLayoutParams
updatedFloatParamsFlag.flags =
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
windowManager.updateViewLayout(floatView, updatedFloatParamsFlag)
return false
}
})
}
}
return START_NOT_STICKY
}
private fun getDB() {
try {
db.collection("Users").document(FirebaseAuth.getInstance().currentUser!!.uid)
.collection(account).document("balance")
.get()
.addOnSuccessListener { document ->
if (document != null) {
val balanceResult = StringBuffer()
balanceResult.append(document.data?.getValue("balance"))
balance = balanceResult.toString()
}
}
} catch (e: Exception){
e.printStackTrace()
}
}
override fun onDestroy() {
super.onDestroy()
stopSelf()
windowManager.removeView(floatView)
}
companion object {
var FOREGROUND_SERVICE_ID = 101
var START = "start"
var STOP_ACTION = "stop"
private const val CHANNEL = "default"
}
}
I have tried several ways of getting the context, but it always seems to come back null. I have tried this, this#Dooa, this.applicationContext, I also created a class MyApp to get context that way. it didn't work either. I used this link. Code below.
class MyApp: Application() {
private var context: Context? = null
override fun onCreate() {
super.onCreate()
context = applicationContext
}
fun getContext(): Context? {
return context?.applicationContext
}
}
I've also checked out this answer about Service is a Context, but I still haven't been able to get this to work.
I have tried the code in the onCreate first then I tried the onStartCommand to no avail. What am I missing?
The window pops up, It pops up with a button click, or from a notification, either way if I click on a view, it gives me the error at the top of this question.

You are doing this:
context = MyApp().getContext()
This is definitely wrong. MyApp is an Android component. You are not allowed to instantiate Android components yourself. Only the Android framework can do this (as it also sets up the Context and other important values.
If you want the Service context, just use this (A Service is a Context).
If you want the application context, just use getApplication() (Application is also a Context).

Related

how to keep onPrgressChanged from updating when I change edit field

I have created a Seekbar and have coded it to update an Editable text field with a number based on what the seekBar is moved to. I also have set the seekBar to be updated when I type into the editable text field. However, what I don't want is the Editable text to update from the seekBar change every time I type one number into the Editabl text field. It is causing the cursor to move to the front of the editabl text field every number I type.
here is some of the code I have:
private const val TAG = "MainActivity"
private const val INITIAL_OUNCE_AMOUNT = 0.0
class MainActivity : AppCompatActivity() {
private lateinit var etEditOunceAmount: EditText
private lateinit var seekBarTip: SeekBar
private lateinit var tvNumberOunces: TextView
private lateinit var seekBarOunces: SeekBar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
etEditOunceAmount = findViewById(R.id.etEditOunceAmount)
seekBarOunces = findViewById(R.id.seekBarOunces)
tvNumberOunces = findViewById(R.id.tvNumberOunces)
seekBarOunces.progress = INITIAL_OUNCE_AMOUNT.toInt()
tvNumberOunces.text = "$INITIAL_OUNCE_AMOUNT"
Below is the meet of whats going on:
seekBarOunces.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
Log.i(TAG, "onProgressChanged $progress")
val str = ("$progress")
val nStr = str.toFloat1()
val aStr = nStr * 0.5
val rStr = aStr.toString()
tvNumberOunces.text = ("$rStr OZ.")
etEditOunceAmount.setText("$rStr")
//computeTipAndTotal()
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
etEditOunceAmount.addTextChangedListener(object : TextWatcher {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (!etEditOunceAmount.equals(null)) {
var NEW_S = s.toString().toFloatOrNull()
if (NEW_S != null) {
NEW_S = NEW_S * 2
}
if (NEW_S != null) {
seekBarOunces.progress = NEW_S.toInt()
}
}
}
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
})
}
}

Swift - toggle model to readonly momentarily

I have a phone number model which looks like this:
import UIKit
import Foundation
struct PhoneValidation : OptionSet {
let rawValue: Int
static let phoneInValid = PhoneValidation(rawValue: 1 << 0)
static let phoneValid = PhoneValidation(rawValue: 1 << 1)
static let smsValidationAttempted = PhoneValidation(rawValue: 1 << 2)
static let smsValidationFailed = PhoneValidation(rawValue: 1 << 3)
static let smsValidationSuccessful = PhoneValidation(rawValue: 1 << 4) // OTP is successfully validated in backend. The field should be non-editable in this duration
static let smsValidationOTPTriggered = PhoneValidation(rawValue: 1 << 5) // OTP validation triggered. The field should be non-editable in this duration
}
class PhonesViewModel: NSCopying {
public var phoneType: PhoneNumberType = PhoneNumberType.mobile
public var phone: String?
public var code: String?
public var countryCode: String?
public var isValid : PhoneValidation?
func copy(with zone: NSZone? = nil) -> Any {
let copy = PhonesViewModel()
copy.phoneType = phoneType
copy.phone = phone
copy.code = code
copy.countryCode = countryCode
copy.isValid = isValid
return copy
}
}
As you can see above the phone model can transition between different states. The SMS validation is available for few countries and for few it is not applicable. So, I plan on setting smsValidationOTPTriggered state when SMS validation is applicable for a country and while the validation is in progress.
What I need here is, while the states smsValidationOTPTriggered or smsValidationSuccessful are set I would not want any module of the application to modify the values(phoneType, phone, code, countryCode) of the model. In other words, I would like the model to switch to a read-only mode while these 2 states are set in model and would like the module to be informed with an error or exception when a modification is attempted.
Is there a best practice already available for what I am trying to achieve here? I have searched before raising this question but did not find any. How can I achieve this?
Thanks,
Raj Pawan Gumdal
How about something like this, I think its better to use property wrappers for your case! The below is not an exact solution but can modify/change to accommodate your need
import UIKit
enum PhoneNumberType {
case mobile
}
enum PhoneValidation {
case phoneInValid
case phoneValid
case smsValidationAttempted
case smsValidationFailed
case smsValidationSuccessful
case smsValidationOTPTriggered
}
struct PhonesViewModel {
public var phoneType: PhoneNumberType = PhoneNumberType.mobile
public var phone: String?
public var code: String?
public var countryCode: String?
public var phoneValidation : PhoneValidation?
func validate(value: [PhoneValidation]) -> Bool {
//add proper check here
return false
}
}
#propertyWrapper
struct Wrapper {
private(set) var value: PhonesViewModel? = nil
var validators: [PhoneValidation] = []
var wrappedValue: PhonesViewModel? {
get { value }
set {
if let model = newValue, model.validate(value: validators) {
value = newValue
print("Value assigned")
} else {
print("Value not assigned")
}
}
}
}
struct SomeOtherClass {
#Wrapper(validators: [PhoneValidation.phoneInValid])
var model: PhonesViewModel?
}
var a = SomeOtherClass()
a.model = PhonesViewModel()
a.model = PhonesViewModel()
You can use a technique with the name "popsicle immutability". An object is initially mutable, but can be "frozen". Modifications for frozen objects are forbidden. In your case PhonesViewModel become frozen when isValid property have value smsValidationOTPTriggered or smsValidationSuccessful.
Let's add Freezable protocol for requirements to objects that can become immutable and conforming for PhonesViewModel:
protocol Freezable: class {
var isFrozen: Bool { get }
}
extension PhonesViewModel: Freezable {
var isFrozen: Bool {
isValid == .smsValidationOTPTriggered || isValid == .smsValidationSuccessful
}
}
Now we must add validation for isFrozen value when a property is assigned. It can be added in property observers like:
...
public var phone: String? {
didSet {
validate()
}
}
...
private func validate() {
assert(!isFrozen)
}
Or using property wrapper:
#propertyWrapper
struct Guarded<Value> {
private var value: Value
init(wrappedValue: Value) {
value = wrappedValue
}
#available(*, unavailable)
var wrappedValue: Value {
get { fatalError("only works on instance properties of classes that conforms to Freezable protocol") }
set { fatalError("only works on instance properties of classes that conforms to Freezable protocol") }
}
static subscript<EnclosingSelf: Freezable>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
) -> Value {
get {
object[keyPath: storageKeyPath].value
}
set {
precondition(!object.isFrozen, "Object \(object) is frozen! Modifications are forbidden")
object[keyPath: storageKeyPath].value = newValue
}
}
}
So your class will look like:
class PhonesViewModel: NSCopying {
#Guarded
public var phoneType: PhoneNumberType = PhoneNumberType.mobile
#Guarded
public var phone: String?
#Guarded
public var code: String?
#Guarded
public var countryCode: String?
#Guarded
public var isValid : PhoneValidation?
func copy(with zone: NSZone? = nil) -> Any {
let copy = PhonesViewModel()
copy.phoneType = phoneType
copy.phone = phone
copy.code = code
copy.countryCode = countryCode
copy.isValid = isValid
return copy
}
}

Turning Java object class into Swift class

So I'm learning how to make my Android apps into an iOS app. I've gotten pretty far though I'm having an issue when it comes creating the object. In my android app I read in a csv file and create a FireworkList object that contains info. Here is the code from my Android app:
import java.util.ArrayList;
public class FireworksList{
private ArrayList<Integer> _category = new ArrayList<>();
private ArrayList<String> _name = new ArrayList<>();
private ArrayList<String> _shotCount = new ArrayList<>();
private ArrayList<String> _price = new ArrayList<>();
private ArrayList<String> _description = new ArrayList<>();
private ArrayList<String> _videoUrl = new ArrayList<>();
private ArrayList<Integer> _imageResourceNumber = new ArrayList<>();
private ArrayList<Boolean> _favorite = new ArrayList<>();
private ArrayList<Boolean> _special = new ArrayList<>();
private Integer _specialCategoryNumer =15;
private Integer _nextId;
public FireworksList() {
_nextId=0;
}
public ArrayList<Integer> get_category() {
return _category;
}
public ArrayList<String> get_name() {
return _name;
}
public ArrayList<String> get_shotCount() {
return _shotCount;
}
public ArrayList<String> get_price() {
return _price;
}
public ArrayList<String> get_description() {
return _description;
}
public ArrayList<String> get_videoUrl() {
return _videoUrl;
}
public ArrayList<Integer> get_imageResourceNumber() {
return _imageResourceNumber;
}
public ArrayList<Boolean> get_favorite() {
return _favorite;
}
public void set_favorite(int index, Boolean bool) {
_favorite.set(index,bool);
}
public ArrayList<Boolean> get_special(){return _special;}
public void Add(int cat, String name, String shot, String price, String description, String video, int image, Boolean fav, Boolean special){
_category.add(_nextId,cat);
_name.add(_nextId,name);
_shotCount.add(_nextId,shot);
_price.add(_nextId,price);
_description.add(_nextId,description);
_videoUrl.add(_nextId,video);
_imageResourceNumber.add(_nextId,image);
_favorite.add(_nextId,fav);
_special.add(_nextId,special);
_nextId++;
}
public int Count(){
return _nextId;
}
public FireworksList CategorySort(int position){
FireworksList fireworksListTemp = new FireworksList();
for(int i=0; i<_nextId;i++){
if(position==0){
fireworksListTemp.Add(_category.get(i),_name.get(i),_shotCount.get(i),_price.get(i),_description.get(i),_videoUrl.get(i),_imageResourceNumber.get(i),_favorite.get(i),_special.get(i));
}
else if(position==_category.get(i)){
fireworksListTemp.Add(_category.get(i),_name.get(i),_shotCount.get(i),_price.get(i),_description.get(i),_videoUrl.get(i),_imageResourceNumber.get(i),_favorite.get(i),_special.get(i));
}
else if(position==_specialCategoryNumer&&_special.get(i)==true){
fireworksListTemp.Add(_category.get(i),_name.get(i),_shotCount.get(i),_price.get(i),_description.get(i),_videoUrl.get(i),_imageResourceNumber.get(i),_favorite.get(i),_special.get(i));
}
}
return fireworksListTemp;
}
public FireworksList FavoriteSort(){
FireworksList fireworksListTemp = new FireworksList();
for(int i = 0; i<_nextId;i++){
if(_favorite.get(i)==true){
fireworksListTemp.Add(_category.get(i),_name.get(i),_shotCount.get(i),_price.get(i),_description.get(i),_videoUrl.get(i),_imageResourceNumber.get(i),_favorite.get(i),_special.get(i));
}
}
return fireworksListTemp;
}
public int FindIndex(String name, FireworksList fireworksList){
int found=0;
for(int j=0;j<fireworksList.Count();j++){
if(fireworksList.get_name().get(j).equals(name)){
found=j;
}
}
return found;
}
}
I've started rewriting my code yet I ran into an issue when it came to creating a new Firework list then appending to it with what is needed in the categorySort function. Here is my modified code so far:
class FireworkList {
var _category = [Int]()
var _name = [String]()
var _shotCount = [String]()
var _price = [String]()
var _description = [String]()
var _videioUrl = [String]()
var _imageResourceNumber = [Int]()
var _favorite = [Bool]()
var _special = [Bool]()
private var _specialCategoryNumber : Int = 15
private var _nextId : Int = 0
func FireworksList(){_nextId = 0}
func get_category() -> Array<Int>{return _category}
func get_name() -> Array<String>{return _name}
func get_shotCount() -> Array<String>{return _shotCount}
func get_price() -> Array<String>{return _price}
func get_discription() -> Array<String>{return _description}
func get_videoUrl() -> Array<String>{return _videioUrl}
func get_imageResourceNumber() -> Array<Int>{return _imageResourceNumber}
func get_favorite() -> Array<Bool>{return _favorite}
func get_special() -> Array<Bool>{return _special}
func set_favorite(index : Int, bool : Bool){_favorite[index] = bool}
func add(cat : Int, name : String, shot : String, price : String, description : String, video : String, image : Int, fav : Bool, special : Bool){
_category.insert(cat, at: _nextId)
_name.insert(name, at: _nextId)
_shotCount.insert(shot, at: _nextId)
_price.insert(price, at: _nextId)
_description.insert(description, at: _nextId)
_videioUrl.insert(video, at: _nextId)
_favorite.insert(fav, at: _nextId)
_special.insert(special, at: _nextId)
_nextId+=1
}
func Count()->Int{return _nextId}
func CategorySort(position : Int)-> [FireworkList]{
var fireworkListTemp = [FireworkList]()
for i in 0..._nextId{
if(position == 0){
fireworkListTemp.append(FireworkList())
}
}
return fireworkListTemp
}
}
I'm thankful for any help.
The main issue here is that your java class is poorly designed and you are taking this design with you when writing it in swift. Rather than having a bunch of properties that are collections you should create a struct for those properties and have one array with the struct. (I didn't include all properties to keep the code shorter).
struct Firework {
var category: Int
var name: String
var shotCount: String
var price: String
var special: Bool
}
and then declare it in the main class
class FireworkList {
private var fireworks = [Firework]()
Below is the class with the add, count and categorySort functions to show some examples of how to use this struct. As you can see this means much less code. I also taken the liberty to rename properties and functions to follow recommended swift naming practices.
For the categorySort I have made use of the high order function filter to collect the correct items. If you are going to work with swift I would recommend you learn more about it and the other high order functions like sort, map etc.
Also worth mentioning to someone coming from java is that we don't use get/set methods to the same extent in swift. We most access the property directly like let x = myObject.someValue or myObject.someValue = 10
class FireworkList {
var fireworks = [Firework]()
private let specialCategoryNumber = 15
func add(cat : Int, name : String, shot : String, price : String, special: Bool) {
self.fireworks.append(Firework(category: cat, name: name, shotCount: shot, price: price, special: special))
}
func count() -> Int { return fireworks.count }
func categorySort(position : Int) -> [Firework] {
switch position {
case 0:
return self.fireworks
case specialCategoryNumber:
return self.fireworks.filter { $0.category == specialCategoryNumber && $0.special}
default:
return self.fireworks.filter {$0.category == position}
}
}
}
Here's how I would do it:
public class FireworksList {
private var _category = [Int]()
private var _name = [String]()
private var _shotCount = [String]()
private var _price = [String]()
private var _description = [String]()
private var _videoURL = [String]()
private var _imageResourceNumber = [Int]()
public var favorite = [Bool]()
private var _special = [Bool]()
private let specialCategoryNumber = 15
private var nextId = 0
public var category: [Int] {
get {
return _category
}
}
public var name: [String] {
get {
return _name
}
}
public var shotCount: [String] {
get {
return _shotCount
}
}
public var price: [String] {
get {
return _price
}
}
public var description: [String] {
get {
return _description
}
}
public var videoURL: [String] {
get {
return _videoURL
}
}
public var imageResourceNumber: [Int] {
get {
return _imageResourceNumber
}
}
public var special: [Bool] {
get {
return _special
}
}
public var count: Int {
get {
return nextId
}
}
public func add( cat: Int, name: String, shot: String, price: String, description: String, video: String, image: Int, fav: Bool, special: Bool) {
self._category.append(cat)
self._name.append(name)
self._shotCount.append(shot)
self._price.append(price)
self._description.append(description)
self._videoURL.append(video)
self._imageResourceNumber.append(image)
self.favorite.append(fav)
self._special.append(special)
nextId += 1
}
public func categorySort(at position: Int) -> FireworksList {
let tmp = FireworksList()
for i in 0...nextId {
if position == 0 || position == category[i] ||
(position == specialCategoryNumber && special[i] == true) {
tmp.add(cat: category[i], name: name[i], shot: shotCount[i], price: price[i], description: description[i], video: videoURL[i], image: imageResourceNumber[i], fav: favorite[i], special: special[i])
}
}
return tmp
}
public func favouriteSort() -> FireworksList {
let tmp = FireworksList()
for i in 0...nextId {
if favorite[i] {
tmp.add(cat: category[i], name: name[i], shot: shotCount[i], price: price[i], description: description[i], video: videoURL[i], image: imageResourceNumber[i], fav: favorite[i], special: special[i])
}
}
return tmp
}
// I'm putting this function as static as it isn't directly related to any particular instance of FireworksList at any point of time.
public static func findIndex(name: String, fireworksList: FireworksList) -> Int {
var found = 0
for j in 0...fireworksList.count {
if fireworksList.name[j] == name {
found = j
}
}
return found
}
}
My code simply almost directly follows your Java code.
This is my first time trying to contribute in Stack Overflow, and I hope this can be helpful to you.

Confused about LiveData Observe

My situation is When user enter loading fragment, check LoggedIn, true go straight to MainFragment, false jump to LoginFramgnet.
here is LoadingFragment:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Logger.t(LoadingFragment::class.java.simpleName).i("onCreateView")
val binding = LoadingFragmentBinding.inflate(inflater, container, false)
subscribeUi()
return binding.root
}
fun subscribeUi(){
val factory: LoadingViewModelFactory = InjectorUtils.provideLoadingViewModelFactory()
val viewModel: LoadingViewModel = ViewModelProviders.of(this, factory).get(LoadingViewModel::class.java)
Logger.t(LoadingFragment::class.java.simpleName).i("viewModel = " + viewModel.toString())
viewModel.checkLogin()
viewModel.isToLogin.observe(viewLifecycleOwner, Observer {
if (it){
findNavController().navigate(R.id.action_loading_fragment_to_garden_fragment)
}else{
Logger.t(LoadingFragment::class.java.simpleName).i("to start login")
findNavController().navigate(R.id.start_login)
}
})
}
here is LoadingViewModel:
class LoadingViewModel(
private val loadingRepository: LoadingRepository
) : ViewModel() {
val isToLogin: MediatorLiveData<Boolean> = MediatorLiveData()
fun checkLogin(){
isToLogin.addSource(loadingRepository.checkLogin()) {
isToLogin.value = it
}
}
}
here is the Loadingrepository:
fun checkLogin() : MutableLiveData<Boolean> {
val data: MutableLiveData<Boolean> = MutableLiveData()
api.httpGet(SDBUrl.CHECK_LOGIN).enqueue(object : Callback<Map<String, Any>>{
override fun onFailure(call: Call<Map<String, Any>>, t: Throwable) {
data.value = false
}
override fun onResponse(call: Call<Map<String, Any>>, response: Response<Map<String, Any>>) {
val result = response.body()
if (result != null && result.containsKey("success")){
val isLogin = result["success"] as Boolean
data.value = isLogin
}else{
data.value = false
}
}
})
return data
}
when logged in, popbackto LoadingFragment,isToLogin observe execute else immediately, LoginFragment start agagin. when I debug, wait a while on LoginFragment popBackStack, then goback to Loading Fragment,isToLogin observe execute true.so I am very confused, how can I fix this.
Finally, I solved this problem as follow.
class LoadingViewModel(
private val loadingRepository: LoadingRepository
) : ViewModel() {
fun checkLogin(): MediatorLiveData<Boolean> {
val isToLogin : MediatorLiveData<Boolean> = MediatorLiveData()
isToLogin.addSource(loadingRepository.checkLogin()) {
Logger.t(LoadingViewModel::class.java.simpleName).i("it = $it")
isToLogin.value = it
}
return isToLogin
}
}
then in LoadingFragment,:
loadingViewModel.checkLogin().observe(viewLifecycleOwner, Observer {
Logger.i("isToLogin = $it")
if (it) {
findNavController().navigate(R.id.action_loading_fragment_to_garden_fragment)
} else {
findNavController().navigate(R.id.start_login)
}
})

Android LiveData: Unable to receive updates on Observable?

I am using android Architecture Component LiveData to notify UI using Observable LiveData but it is not getting triggered. below is the code snippet.
AuthRepository
class AuthRepository(private val repository:Avails) {
fun auth(mobile: String): LiveData<Boolean>{
var data: MutableLiveData<Boolean> = MutableLiveData()
repository.auth(prepareHeaders(), AuthRequest(mobile))
.enqueue(object : Callback<AuthResponse>{
override fun onResponse(call: Call<AuthResponse>, response: Response<AuthResponse>) {
if(response.isSuccessful){
data.value = true
}
}
override fun onFailure(call: Call<AuthResponse>, t: Throwable) {
data.value = false
}
})
return data
}
}
LoginViewModel
class LoginViewModel(private val repository: AuthRepository) : ViewModel() {
var _showOtpScreen: MutableLiveData<Boolean> = MutableLiveData()
fun auth(mobile: String){
_showOtpScreen.value = repository.auth(mobile).value
}
}
LoginFragment
class LoginFragment : Fragment() {
private lateinit var loginViewModel: LoginViewModel
companion object {
private const val sTag: String = "LoginFragment"
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val authRepository = AuthRepository(AvailsClient.retrofit.create(Avails::class.java))
loginViewModel = ViewModelProviders.of(this,LoginViewModelFactory(authRepository)).get(LoginViewModel::class.java)
loginViewModel._showOtpScreen.observe(this, Observer {
if(it != null){
if(it){
Log.e(sTag,"OTP RECEIVED")
findNavController().navigate(R.id.action_loginFragment_to_verifyOtpFragment)
}else{
Log.e(sTag,"Failed to get OTP")
}
}
})
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_login, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnContinue.setOnClickListener {
loginViewModel.auth(edtPhoneNumber.text.toString())
}
}
}
Above code is not able to Observe _showOtpScreen it is only called once with null value and never gets called again when service call completes.
Event wrapper is the solution for above problem.

Resources