10

I have entity:

@Entity(tableName = "products")
public class Product{

    @PrimaryKey(autoGenerate = true)
    private Long id;

    @ColumnInfo(name = "amount")
    private BigDecimal amount;

I need store it in Room DB. I Can not store BigDecimal and I create converter:

public static class Converters {
        @TypeConverter
        public BigDecimal fromString(String value) {
            return value == null ? null : new BigDecimal(value);
        }

        @TypeConverter
        public Double amountToString(BigDecimal bigDecimal) {
            if (bigDecimal == null) {
                return null;
            } else {
                return bigDecimal.doubleValue();
            }
        }
    }

and add to column:

@TypeConverters(Converters.class)
    @ColumnInfo(name = "amount")
    private BigDecimal amount;

Now I store currency in double column. And I want method for summ totalcurrency:

@Query("SELECT SUM(amount) FROM products")
LiveData<Double> totalSum();

But I think it is bad way, because I can lose some values when converting.

My question: How can I ctore currency in ROOM DB? and return method

LiveData<BigDecimal> totalSum();
ADM
  • 18,477
  • 11
  • 47
  • 78
ip696
  • 5,816
  • 11
  • 56
  • 116

2 Answers2

10

Instead of Long vs BigDecimal converters, I'm using between String and BigDecimal.

Warning: With this way, you can't use SUM, TOTAL or other expression with Room DB. Because my purpose for app is using many currencies, so I manually retrieve data and calculate based on current exchange rates.

class BigDecimalTypeConverter {

    @TypeConverter
    fun bigDecimalToString(input: BigDecimal?): String {
        return input?.toPlainString() ?: ""
    }

    @TypeConverter
    fun stringToBigDecimal(input: String?): BigDecimal {
        if (input.isNullOrBlank()) return BigDecimal.valueOf(0.0)
        return input.toBigDecimalOrNull() ?: BigDecimal.valueOf(0.0)
    }

}

if you only have one currency, you can use Double instead of String:

class BigDecimalDoubleTypeConverter {

    @TypeConverter
    fun bigDecimalToDoubleinput: BigDecimal?): Double {
        return input?.toDoubleOrNull() ?: 0.0
    }

    @TypeConverter
    fun stringToBigDecimal(input: Double?): BigDecimal {
        if (input == null) return BigDecimal.ZERO
        return BigDecimal.valueOf(input) ?: BigDecimal.ZERO
    }

}

So you can retrieve SUM, TOTAL,... from Dao:

@Query("SELECT SUM(transactions.amounts) FROM transactions WHERE transactions.timestamp >= :fromDateTimestamp")
fun getTotalAmount(fromDateTimestamp: Long): Double

Hope it help!

dphans
  • 1,207
  • 14
  • 19
8

You can store your value in long. If you have eg. 1.55 when serializing multiply it by 100 and store long in db. When you deserialize create BigDecimal from this long and divide it by 100

 public static class Converters {
        @TypeConverter
        public BigDecimal fromLong(Long value) {
            return value == null ? null : new BigDecimal(value).divide(new BigDecimal(100));
        }

        @TypeConverter
        public Long toLong(BigDecimal bigDecimal) {
            if (bigDecimal == null) {
                return null;
            } else {
                return bigDecimal.multiply(new BigDecimal(100)).longValue();
            }
        }
    }
Filip P.
  • 994
  • 6
  • 16
  • 2
    I like this solution, but unfortunately it doesn't account for countries that have a different number of decimal places in their currency. See this Stack Overflow question (ignore the fact that is pertaining to iPhone development): (https://stackoverflow.com/questions/2701301/various-countrys-currency-decimal-places-width-in-the-iphone-sdk) – shagberg Sep 04 '18 at 18:28