I am doing an application in Kotlin, where questions are added to the DB. I am getting an error like: no value passed for parameter string
if (cursor.moveToFirst()) {
do {
val question = Question() //this line gives the error
question.setID(cursor.getString(0).toInt())
question.setQuestion(cursor.getString(1))
question.setOption1(cursor.getString(2))
question.setOption2(cursor.getString(3))
question.setOption3(cursor.getString(4))
question.setOption4(cursor.getString(5))
question.setAnswer(cursor.getString(6))
// Adding contact to list
questionList.add(question)
} while (cursor.moveToNext())
}
Question Class
class Question(
toInt: Int,
string: String,
string1: String,
string2: String,
string3: String,
string4: String,
string5: String
) {
var _id = 0
var _question: String? = null
var _option1: String? = null
var _option2: String? = null
var _option3: String? = null
var _option4: String? = null
var _answer: String? = null
fun Question() {}
fun Question(
id: Int,
question: String?,
option1: String?,
option2: String?,
option3: String?,
option4: String?,
_answer: String?
) {
_id = id
_question = question
_option1 = option1
_option2 = option2
_option3 = option3
_option4 = option4
this._answer = _answer
}
fun Question(
question: String?,
option1: String?,
option2: String?,
option3: String?,
option4: String?,
_answer: String?
) {
_question = question
_option1 = option1
_option2 = option2
_option3 = option3
_option4 = option4
this._answer = _answer
}
fun getID(): Int {
return _id
}
fun setID(id: Int) {
_id = id
}
fun getQuestion(): String? {
return _question
}
fun setQuestion(question: String?) {
_question = question
}
fun getOption1(): String? {
return _option1
}
fun setOption1(option1: String?) {
_option1 = option1
}
fun getOption2(): String? {
return _option2
}
fun setOption2(option2: String?) {
_option2 = option2
}
fun getOption3(): String? {
return _option3
}
fun setOption3(option3: String?) {
_option3 = option3
}
fun getOption4(): String? {
return _option4
}
fun setOption4(option4: String?) {
_option4 = option4
}
fun getAnswer(): String? {
return _answer
}
fun setAnswer(answer: String?) {
_answer = answer
}
}
This line is giving me the error
val question = Question()
First, it seems you are trying to create some extra constructors with the syntax
fun Question() {
but this is not the syntax for a constructor. It is a regular member function named Question and can only be called on an already existing instance of a Question. The correct syntax would be:
constructor() {
However, since you have a primary constructor, this would be a secondary constructor and would have to call the primary constructor with the arguments it requires, which is going to be super complicated for your case.
Your Question class is extremely overdesigned. In Kotlin:
There is no reason to have setter functions since we have properties instead of fields. Any setter function is redundant to the capabilities of a property, so there is no reason to pollute your code with them.
You can put properties directly in the primary constructor. Then you don't have to manually assign constructor parameters to property values.
You can give parameters default values so you don't have to create special constructors that represent empty conditions.
With the above features of Kotlin, you can rewrite your entire class like this, with no loss of functionality or flexibility:
data class Question (
var id = 0,
var question: String? = null,
var option1: String? = null,
var option2: String? = null,
var option3: String? = null,
var option4: String? = null,
var answer: String? = null
)
At your use site, you can call this empty constructor and then set the properties on it like you were trying to do. Properties in Kotlin should be set with assignment syntax (=), not setter functions. You can also use the apply scope function to avoid having to write question. over and over:
if (cursor.moveToFirst()) {
do {
val question = Question().apply {
id = cursor.getString(0).toInt()
question = cursor.getString(1)
option1 = cursor.getString(2)
option2 = cursor.getString(3)
option3 = cursor.getString(4)
option4 = cursor.getString(5)
answer = cursor.getString(6)
}
// Adding contact to list
questionList.add(question)
} while (cursor.moveToNext())
}
However, it looks like you have no reason to make any of these parameters nullable. You should avoid nullable parameters whenever possible, because they are harder to work with. If a Question in your app always has values for all these properties, they should not be nullable, and you don't need to provide default values. I would define your class like this:
data class Question (
val id: Int,
val question: String,
val option1: String,
val option2: String,
val option3: String,
val option4: String,
val answer: String
)
Then at the usage site, you can call this constructor with all the arguments directly:
if (cursor.moveToFirst()) {
do {
val question = Question(
id = cursor.getString(0).toInt(),
question = cursor.getString(1),
option1 = cursor.getString(2),
option2 = cursor.getString(3),
option3 = cursor.getString(4),
option4 = cursor.getString(5),
answer = cursor.getString(6)
)
// Adding contact to list
questionList.add(question)
} while (cursor.moveToNext())
}
By the way, this if/do/while pattern for traversing the cursor is unnecessarily complicated. A cursor starts at a position before the first item, so you don't need to move it to first or use do/while. It would be equivalent to use:
while (cursor.moveToNext()) {
val question = Question(
id = cursor.getString(0).toInt(),
question = cursor.getString(1),
option1 = cursor.getString(2),
option2 = cursor.getString(3),
option3 = cursor.getString(4),
option4 = cursor.getString(5),
answer = cursor.getString(6)
)
// Adding contact to list
questionList.add(question)
}
You have to initialize Question class constructor parameters value to default or null.
For example,
class Question(
toInt: Int = 0,
string: String = "",
string1: String = "",
string2: String = "",
string3: String = "",
string4: String = "",
string5: String = ""
) {
//Your code here
}
OR
class Question(
toInt: Int? = null,
string: String? = null,
string1: String? = null,
string2: String? = null,
string3: String? = null,
string4: String? = null,
string5: String? = null
) {
//Your code here
}
Related
As far as i can see, it's quite a common mistake. However, I have not found a solution to my version of the problem. I want to add a list of new objects to an already created list based on the index. When I want to add each new Data object to the corresponding Sensor object by append(), I get this error: Cannot use mutating member on immutable value: function call returns immutable value
However, I am unable to deal with this error. How to solve it?
function:
func setStationItemsWithData(addItemList: [AddItem], dataList: [Data]) {
var dataItems = [DataItem]()
var addItems = [AddItem]()
addItems.append(contentsOf: addItemList)
let addItemSensors = addItemList.flatMap{$0.sensors}
dataList.forEach { data in
guard let index = addItemSensors.firstIndex(where: {$0.id == data.id})
else {
print("Failed to find a Data for AddItem \(data.id)")
return
}
let dataItem = DataItem(
values: [ValueItem(
date: data.values.first?.date ?? "",
value: data.values.first?.value.value ?? 0.0)])
dataItems.append(dataItem)
addItems.compactMap{$0.sensors[index].data}.append(dataItem) !!!//here in .append() shows the error
}
AddItem:
class AddItem: Object, Identifiable {
#objc dynamic var id: Int = 0
#objc dynamic var stationId: Int = 0
#objc dynamic var cityName: String = ""
#objc dynamic var addressStreet: String = ""
var added = RealmOptional<Bool>()
let sensors = List<SensorItem>()
convenience init(id: Int, stationId: Int, cityName: String, addressStreet: String, added: RealmOptional<Bool>, sensor: [SensorItem]) {
self.init()
self.id = id
self.stationId = stationId
self.cityName = cityName
self.addressStreet = addressStreet
self.added = added
self.sensors.append(objectsIn: sensor)
}
override class func primaryKey() -> String? {
return "id"
}
}
class SensorItem: Object {
#objc dynamic var id: Int = 0
#objc dynamic var stationId: Int = 0
#objc dynamic var param: ParamItem?
#objc dynamic var data: DataItem?
convenience init(id: Int, stationId: Int, param: ParamItem, data: DataItem) {
self.init()
self.id = id
self.stationId = stationId
self.param = param
self.data = data
}
}
class ParamItem: Object {
#objc dynamic var paramName: String = ""
#objc dynamic var paramFormula: String = ""
#objc dynamic var paramCode: String = ""
#objc dynamic var idParam: Int = 0
convenience init(paramName: String, paramFormula: String, paramCode: String, idParam: Int) {
self.init()
self.paramName = paramName
self.paramFormula = paramFormula
self.paramCode = paramCode
self.idParam = idParam
}
}
class DataItem: Object {
#objc dynamic var id: Int = 0
var values = List<ValueItem>()
convenience init(values: [ValueItem]) {
self.init()
self.values.append(objectsIn: values)
}
}
class ValueItem: Object {
#objc dynamic var date: String? = ""
#objc dynamic var value: Double = 0
convenience init(date: String, value: Double) {
self.init()
self.date = date
self.value = value
}
}
So I am working on a project which will allow users to do multiple different quizzes.
My XML, which is hosted online, is of the following format:
<questions>
<question>
<clue> sample clue 1 </clue>
<correct_answer>2</correct_answer>
<enumeration>1</enumeration>
<info> sample info 1 </info>
<location_clue>Sample locationClue (5,5)</location_clue>
<option_a>Ans1</option_a>
<option_b>Ans2</option_b>
<option_c>Ans3</option_c>
</question>
<question>
<clue> sample clue 2 </clue>
<correct_answer>3</correct_answer>
<enumeration>2</enumeration>
<info> sample info 2 </info>
<location_clue>Sample locationClue (4,2)</location_clue>
<option_a>Ans1</option_a>
<option_b>Ans2</option_b>
<option_c>Ans3</option_c>
</question>
</questions>
my parser initiation looks like this:
if let urlString = URL(string: "realURL goes here.xml -- This has an actual url in my code obviously.")
{
if let parser = XMLParser(contentsOf: urlString)
{
parser.delegate = self
parser.parse()
}
}
parserDidStartElement:
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:])
{
thisName = elementName
if thisName == "hunt"
{
}
}
ParserFoundCharacter:
func parser(_ parser: XMLParser, foundCharacters string: String)
{
let data = string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if data.count != 0
{
switch thisName
{
case "clue": questionClue = data
break
case "info": questionInfo = data
break
case "location_clue": locationClue = data
break
case "option_a": questionAnswerA = data
break
case "option_b": questionAnswerB = data
break
case "option_c": questionAnswerC = data
default:
break
}
}
}
This is the HuntDetail.swift class which creates a scruct named QUIZ, inside of this scruct there are currently 4 variables, question, answerA, answerB and answerC:
import Foundation
struct QUIZ {
var question = ""
var answerA = ""
var answerB = ""
var answerC = ""
}
Essentially the app will allow users to take multi-choice quizzes. When answers are selected a progress bar at the top of the interface will indicate their progress through the current quiz.
I was wondering if it would be possible to store the value of: clue,info,location_clue,option_a,b,c... inside of an array, from the array I will start to formulate the actual quiz functionality.
As it stands right now, the application will only display the last element of the data previously mentioned.
I know this is long and probably hard to understand what I'm trying to do but if anyone can help with this it would be greatly appreciated. It should also be noted that, yes, I am fairly new to Swift and iOS development as a whole.
Yes you can do this fairly easily, although I haven't used XMLParser in a while.
Note: In my code below I have renamed your QUIZ to Question as the struct represents a single question, not a whole quiz (list of questions)
So you want an empty array to each item to as you parse it:
var quiz = [Question]() // quiz is a list of questions.
Then you want to track the current question you are working on
var currentQuestion: Question?
So then each time you start and end a Question element, you know you have finished parsing a single question, so add it to the list.
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
if elementName == "Question" {
currentQuestion = Question()
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "Question", let question = currentQuestion {
quiz.append(question)
}
}
So you are just parsing the XML document top to bottom. Once you come upon a opening Question element, create a question object, fill the rest in and then when you encounter a closing question element, add the current question to the list.
By the end of the document your quiz variable should contain all of the questions in the document.
EDIT:
So I had to make a few changes, the foundCharacters can come in parts so we need to keep track of it.
Here is a working playground which returns 2 questions (based on your sample XML above). Answer C is always blank, It looks like this is because of a newline char and the trim is cutting of the text, you may need to remove newlines and then just trim whitespace but this code will give you a good start.
import Foundation
let xmlData = """
<questions>
<question>
<clue> sample clue 1 </clue>
<correct_answer>2</correct_answer>
<enumeration>1</enumeration>
<info> sample info 1 </info>
<location_clue>Sample locationClue (5,5)</location_clue>
<option_a>Ans1</option_a>
<option_b>Ans2</option_b>
<option_c>Ans3</option_c>
</question>
<question>
<clue> sample clue 2 </clue>
<correct_answer>3</correct_answer>
<enumeration>2</enumeration>
<info> sample info 2 </info>
<location_clue>Sample locationClue (4,2)</location_clue>
<option_a>Ans1</option_a>
<option_b>Ans2</option_b>
<option_c>Ans3</option_c>
</question>
</questions>
""".data(using: .utf8)!
struct Question {
var question: String?
var clue: String?
var info: String?
var locationClue: String?
var answerA: String?
var answerB: String?
var answerC: String?
}
class MySuperXMLParser: NSObject, XMLParserDelegate {
private let parser: XMLParser
private var quiz = [Question]()
private var currentQuestion: Question?
private var currentElement: String?
private var foundCharacters = ""
init(data: Data) {
parser = XMLParser(data: data)
super.init()
parser.delegate = self
}
func parse() -> [Question] {
parser.parse()
return quiz
}
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
if elementName == "question" {
currentQuestion = Question()
}
print("Started element: \(elementName)")
currentElement = elementName
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
print("found characters: \(string)")
foundCharacters += string
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
print("ended element: \(elementName), text = \(foundCharacters)")
let text = foundCharacters.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
switch currentElement
{
case "clue":
currentQuestion?.clue = text
break
case "info":
currentQuestion?.info = text
break
case "location_clue":
currentQuestion?.locationClue = text
break
case "option_a":
currentQuestion?.answerA = text
break
case "option_b":
currentQuestion?.answerB = text
break
case "option_c": currentQuestion?.answerC = text
default:
break
}
foundCharacters = ""
if elementName == "question", let question = currentQuestion {
print("adding question: \(question)")
quiz.append(question)
}
}
}
let parser = MySuperXMLParser(data: xmlData)
let quiz = parser.parse()
print(quiz.count, quiz)
If you don't mind using an external library, you can try XMLMapper.
Just use the following model classes:
class Questions: XMLMappable {
var nodeName: String!
var questions: [Question]?
required init(map: XMLMap) {
}
func mapping(map: XMLMap) {
questions <- map["question"]
}
}
class Question: XMLMappable {
var nodeName: String!
var clue: String?
var correct_answer: Int?
var enumeration: Int?
var info: String?
var location_clue: String?
var option_a: String?
var option_b: String?
var option_c: String?
required init(map: XMLMap) {
}
func mapping(map: XMLMap) {
clue <- map["clue"]
correct_answer <- map["correct_answer"]
enumeration <- map["enumeration"]
info <- map["info"]
location_clue <- map["location_clue"]
option_a <- map["option_a"]
option_b <- map["option_b"]
option_c <- map["option_c"]
}
}
And map your XML by calling map(XMLString:) function in XMLMapper:
let object = XMLMapper<Questions>().map(XMLString: xmlString)
print(object?.questions?.first?.clue ?? "nil")
Hope this helps.
I have 2 Types, first is Car with property feature and it of the second type Feature.
I have a property cars: [Car], when I append new car to it, it returns nil.
I have created a sample in the snippet below:
class Car {
let make: String
var features: Feature?
let id: String
init(make: String, features: Feature?, id: String) {
self.make = make
self.features = features
self.id = id
}
}
class Feature {
let convertible: String
init(convertible: String) {
self.convertible = convertible
}
}
var cars: [Car]?
var feature: Feature?
let featureElement = Feature(convertible: "yes")
feature = featureElement
let car = Car(make: "SomeMaked", features: feature, id: "ID")
cars?.append(car)
print(cars)
My question is: Shouldn't the array increase its count after appening to it? What am I missing?
Please note that this code is just sample, so ignore typos and coding convention.
You haven't initialized your array.
Replace your var cars: [Car]? with var cars: [Car]? = []
I have a couple of different types of structs (Promo & Event). I'd like to create an empty array which gets populated with an array of each type depending on the outcome of an if statement.
So something like this:
var dataArray:[Any] = [] // see options I've tried below
if myTest == true {
dataArray = [Promo, Promo, Promo]
} else {
dataArray = [Event, Event, Event]
}
I have tried using:
1. var dataArray: [Any] = []
2. var dataArray: [AnyObject] = []
3. var dataArray: [Any] = [Any]()
4. var dataArray: [AnyObject] = [AnyObject]()
but when I try to store an array of Promo Structs in dataArray I get an error Cannot assign value of type '[Promo]' to type '[Any]' etc.
So, how do I initialise an array so that it can store a variety of (unknown) Structs. Or how do I modify my Structs to enable them to be stored in an array?
I'm really struggling to see what I'm doing wrong so any pointers would be v. helpful.
Here are my Structs:
Promo.swift
import Foundation
struct Promo {
// initialise the stored properties for use later
let promoId : Int
let date : NSDate
let title: String
let body: String
let busName : String
let busId : Int
let categoryId: Int
let featured: Bool
// a universal init() method which has optional parameters
init(promoId: Int,
date: NSDate,
title: String,
body: String,
busName: String,
busId: Int,
categoryId: Int,
featured: Bool
){
self.promoId = promoId
self.date = date
self.title = title
self.body = body
self.busName = busName
self.busId = busId
self.categoryId = categoryId
self.featured = featured
}
}
// allow us to compare structs
extension Promo: Equatable {}
func ==(lhs: Promo, rhs: Promo) -> Bool {
return lhs.promoId == rhs.promoId
&& lhs.date == rhs.date
&& lhs.title == rhs.title
&& lhs.body == rhs.body
&& lhs.busName == rhs.busName
&& lhs.busId == rhs.busId
&& lhs.categoryId == rhs.categoryId
&& lhs.featured == rhs.featured
}
Event.swift
import Foundation
struct Event {
// initialise the stored properties for use later
let eventId : Int
let date : NSDate
let title: String
let body: String
let busName : String
let busId : Int
let categoryId: Int
// a universal init() method which has optional parameters
init(eventId: Int,
date: NSDate,
title: String,
body: String,
busName: String,
busId: Int,
categoryId: Int
){
self.eventId = eventId
self.date = date
self.title = title
self.body = body
self.busName = busName
self.busId = busId
self.categoryId = categoryId
}
}
This may not be exactly what you intended, but you can make this a bit cleaner by using classes instead of structs. It appears that a 'Promo' is just an 'Event' with one extra data member (featured)... If that's the case, then you can rename the Promo.promoId field Promo.eventId, and then make it a subclass of Event. Like this:
class Promo : Event {
let featured: Bool
// a universal init() method which has optional parameters
init(eventId: Int,
date: NSDate,
title: String,
body: String,
busName: String,
busId: Int,
categoryId: Int,
featured: Bool
){
self.featured = featured
super.init(eventId: eventId, date: date, title: title, body: body, busName: busName, busId: busId, categoryId: categoryId)
}
}
Then just create the data array like this:
var dataArray = [Event]()
if myTest == true {
dataArray = [promo1, promo2, promo3]
} else {
dataArray = [event1, event2, event3]
}
To use the featured member for a Promo you'll still need to cast like this:
if let thisPromo = dataArray[0] as? Promo {
print(thisPromo.featured)
}
If you are trying to assign to dataArray from [Promo] or [Event] arrays, you could map:
var dataArray:[Any] = []
var promoArray:[Promo] = [Promo(), Promo(), Promo()]
var eventArray:[Event] = [Event(), Event(),Event()]
if myTest == true {
dataArray = promoArray.map { $0 as Any }
} else {
dataArray = eventArray.map { $0 as Any }
}
Or create new Any arrays:
if myTest == true {
dataArray = Array<Any>(arrayLiteral: promoArray)
} else {
dataArray = Array<Any>(arrayLiteral: eventArray)
}
The code below is creating a new map called nameTable, then adding an entry named example to it, then trying to print the name property of the Value.
When I run it, it seems that the plus operation didn't add a new entry to the map like I thought it would.
So what am I doing wrong?
class Person(name1: String, lastName1: String, age1: Int){
var name: String = name1
var lastName: String = lastName1
var age: Int = age1
}
var nameTable: MutableMap<String, Person> = mutableMapOf()
var example = Person("Josh", "Cohen", 24)
fun main (args: Array<String>){
nameTable.plus(Pair("person1", example))
for(entry in nameTable){
println(entry.value.age)
}
}
While we're at it, I would love some examples of how to add, remove, and get an entry from a map.
The reason for your confusion is that plus is not a mutating operator, meaning that it works on (read-only) Map, but does not change the instance itself. This is the signature:
operator fun <K, V> Map<out K, V>.plus(pair: Pair<K, V>): Map<K, V>
What you want is a mutating operator set, defined on MutableMap:
operator fun <K, V> MutableMap<K, V>.set(key: K, value: V)
So your code may be rewritten (with some additional enhancements):
class Person(var name: String, var lastName: String, var age: Int)
val nameTable = mutableMapOf<String, Person>()
val example = Person("Josh", "Cohen", 24)
fun main (args: Array<String>) {
nameTable["person1"] = example
for((key, value) in nameTable){
println(value.age)
}
}
The plus-method on Map creates a new map that contains the new entry. It does not mutate the original map. If you want to use this method, you would need to do this:
fun main() {
val table = nameTable.plus(Pair("person1", example))
for (entry in table) {
println(entry.value.age)
}
}
If you want to add the entry to the original map, you need to use the put method like in Java.
This would work:
fun main() {
nameTable.put("person1", example)
for (entry in nameTable) {
println(entry.value.age)
}
}
To get and remove entries from the MutableMap, you can use this:
nameTable["person1"] // Syntactic sugar for nameTable.get("person1")
nameTable.remove("person1")
It's too much trouble,You can assign values directly,like this:
#Test
#Throws(Exception::class)
fun main(){
val map:MutableMap<String,Person> = mutableMapOf()
map["Josh"]= Person("Josh", "Cohen", 24)
map.forEach { t, u ->
println("map key:$t,map value:${u.toString()}")
}
}
class Person(name1:String, lastName1:String, age1:Int){
var name:String = name1
var lastName:String = lastName1
var age:Int = age1
override fun toString(): String {
return "name:$name,lastNam:$lastName,age:$age \n"
}
}
You have to use
put
method.
class Person(name1:String, lastName1:String, age1:Int){
var name:String = name1
var lastName:String = lastName1
var age:Int = age1
}
var nameTable:MutableMap<String, Person> = mutableMapOf()
var example = Person("Josh", "Cohen", 24)
fun main (args: Array<String>){
nameTable.put("person1", example)
for(entry in nameTable){
println(entry.value.age)
}
}
val params: MutableMap<String, String> = HashMap<String, String>()
params.put("1", "A")
params.put("2", "B")