Reputation: 769
This is what I am doing to display the amount with two decimal places. It is working fine, but I would like to know if this is right or is there any drawback in doing so and what is the better way to do it?
I want to format the amount for 'Your Price' and 'MRP'. How can I do that?
holder.itemView.tv_dashboard_item_title.text = model.title
holder.itemView.tv_dashboard_item_price.text = "Your Price ₹${model.price}"
holder.itemView.tv_dashboard_item_mrp.text = "MRP ₹${model.mrp}"
val mrp:Double? = model.mrp
val price: Double? = model.price
val save=DecimalFormat("#,##0.00")
val save2: Double = mrp!! - price!!
val saves=save.format(save2)
holder.itemView.tv_dashboard_item_you_save.text = "You Save ₹ $saves"
Thank you.
EDIT
Revised code.
val decFormat = DecimalFormat("#,##0.00")
holder.itemView.tv_dashboard_item_title.text = model.title
val mrp: BigDecimal = model.mrp.toBigDecimal()
val price: BigDecimal = model.price.toBigDecimal()
val save: BigDecimal = mrp - price
val saveAmount = decFormat.format(save)
holder.itemView.tv_dashboard_item_price.text = decFormat.format(price)
holder.itemView.tv_dashboard_item_mrp.text = decFormat.format(mrp)
holder.itemView.tv_dashboard_item_you_save.text = "You Save ₹ $saveAmount"
EDIT 2
Following is the model class Product.kt
@Parcelize
data class Product(
val user_id: String = "",
val user_name: String = "",
val title: String = "",
val mrp: BigDecimal= "0.00".toBigDecimal(),
val price: BigDecimal = "0.00".toBigDecimal(),
val description: String = "",
val stock_quantity: String = "",
val image: String = "",
var brand_name:String="",
var manufacturer:String="",
var main_category:String="",
var sub_category:String="",
var product_id: String = "",
) : Parcelable
Function in AddProductActivity.kt
to upload product details
private fun uploadProductDetails() {
val username =
this.getSharedPreferences(Constants.TRAD_PREFERENCES, Context.MODE_PRIVATE)
.getString(Constants.LOGGED_IN_USERNAME, "")!!
val product = Product(
FirestoreClass().getCurrentUserID(),
username,
et_product_title.text.toString().trim { it <= ' ' },
et_product_mrp.text.toString().toBigDecimal(),
et_product_price.text.toString().toBigDecimal(),
et_product_description.text.toString().trim { it <= ' ' },
et_product_quantity.text.toString().trim { it <= ' ' },
mProductImageURL,
et_product_brand_name.text.toString().trim { it <= ' ' },
et_product_manufacturer.text.toString().trim { it <= ' ' },
et_product_main_category.text.toString().trim { it <= ' ' },
et_product_sub_category.text.toString().trim { it <= ' ' },
)
FirestoreClass().uploadProductDetails(this@AddProductActivity, product)
}
Logcat
--------- beginning of crash
2021-06-19 18:36:11.605 6674-6674/com.trad E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.trad, PID: 6674
java.lang.IllegalArgumentException: Could not serialize object. Numbers of type BigDecimal are not supported, please use an int, long, float or double (found in field 'mrp')
at com.google.firebase.firestore.util.CustomClassMapper.serializeError(CustomClassMapper.java:555)
at com.google.firebase.firestore.util.CustomClassMapper.serialize(CustomClassMapper.java:122)
at com.google.firebase.firestore.util.CustomClassMapper.access$400(CustomClassMapper.java:54)
at com.google.firebase.firestore.util.CustomClassMapper$BeanMapper.serialize(CustomClassMapper.java:902)
at com.google.firebase.firestore.util.CustomClassMapper.serialize(CustomClassMapper.java:178)
at com.google.firebase.firestore.util.CustomClassMapper.serialize(CustomClassMapper.java:104)
at com.google.firebase.firestore.util.CustomClassMapper.convertToPlainJavaTypes(CustomClassMapper.java:78)
at com.google.firebase.firestore.UserDataReader.convertAndParseDocumentData(UserDataReader.java:231)
at com.google.firebase.firestore.UserDataReader.parseMergeData(UserDataReader.java:87)
at com.google.firebase.firestore.DocumentReference.set(DocumentReference.java:166)
at com.trad.firestore.FirestoreClass.uploadProductDetails(FirestoreClass.kt:246)
at com.trad.ui.activities.AddProductActivity.uploadProductDetails(AddProductActivity.kt:280)
at com.trad.ui.activities.AddProductActivity.imageUploadSuccess(AddProductActivity.kt:254)
at com.trad.firestore.FirestoreClass$uploadImageToCloudStorage$1$1.onSuccess(FirestoreClass.kt:212)
at com.trad.firestore.FirestoreClass$uploadImageToCloudStorage$1$1.onSuccess(FirestoreClass.kt:27)
at com.google.android.gms.tasks.zzn.run(com.google.android.gms:play-services-tasks@@17.2.0:4)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8512)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
Upvotes: 5
Views: 13972
Reputation: 18537
Using a DecimalFormat
is a perfectly good way to format a number.
However, you have a deeper issue here, which is that you should never use a Float or Double to store a value that needs to be exact, such as money.
That's because Floats and Doubles are stored as binary fractions, not decimal fractions. And just as you can't represent 1/3 exactly as a decimal fraction of any finite length (0.33333333333…), so you can't represent 1/10 exactly as a binary fraction (0.0001100110011…). So most of the decimal numbers you want to store will get approximated and rounded to the nearest binary fraction that can be stored. This isn't always obvious — when printing them out, they get rounded again to the nearest decimal fraction, and in many cases that ‘recovers’ the number you want — but there are many cases where it's noticeable, especially as the result of calculations.
You can see the effect in the Kotlin REPL:
>>> 0.1 + 0.2
res0: kotlin.Double = 0.30000000000000004
In this case, the binary fractions nearest to 0.1 and 0.2 sum to give a binary fraction that's nearer to 0.30000000000000004 than it is to 0.3.
(There are many existing questions on StackOverflow discussing this, such as here.)
So if you need your money values to be accurate (and you almost always do!), then you should store them some other way. For example, if you only ever need two decimal places (i.e. the number of paise), then simply store the number of paise as an integer. Or if you don't need to do any calculations, you could store the number as a string (which is otherwise a bad idea…).
However, the most general and flexible way in Kotlin (and Java) is to use BigDecimal. That uses decimal fractions internally to represent any decimal number exactly, to any precision you need, and you can easily do calculations and other manipulations.
In Java, using it is awkward and long-winded, but Kotlin's operator overloading makes it very natural, e.g.:
>>> val p1 = "0.1".toBigDecimal()
>>> val p2 = "0.2".toBigDecimal()
>>> p1 + p2
res3: java.math.BigDecimal = 0.3
DecimalFormat
supports it too:
>>> java.text.DecimalFormat("#,##0.00").format(p1 + p2)
res4: kotlin.String! = 0.30
(Note: do not create a BigDecimal from a Float or Double, as the damage will already have been done. If you have an integer value, then start from an integer type such as Int or Long; otherwise, you'll need to start from a String to get an exact value.)
Upvotes: 8