如何使用 MVP 架构构建 SIP 计算器 Android 应用程序?
SIP,系统投资计划。投资共同基金的方法之一是每周、每月每季度或每半年一次。 SIP 计算器是让投资者了解其投资回报的工具。然而,共同基金的回报因各种因素而异。例如,SIP 计算器不计算绝对回报,因为一些共同基金收取退出负荷和费用比率。因此,它只计算估计收益并提供到期价值的结果。本文讨论了在具有强大 MVP 架构的 Android 中实现 SIP 计算器。查看以下内容以了解最终产品。
SIP回报计算器有何帮助?
与共同基金的一次性投资相比,SIP 提供了非常丰厚的回报。 SIP 计算器可帮助投资者制定长期投资计划,因为它提供以下内容:
- 它决定了一段时间内的总投资额。
- 讲述超过预期回报率的估计回报。
- 还计算到期价值,即估计收益 + 总投资额。
Note: Estimated returns are simply the maturity value – total invested amount.
计算 SIP 回报的公式很简单。
m = p * ( { ( 1 + i ) ^ n – 1 } / i ) * ( i + 1 )
m -> Maturity Value (Estimated Return + Total Amount Invested).
p -> principal amount (to be invested monthly).
i -> period interest rate.
n -> number of payments made.
从此计算器获得的好处
- 根据估计的回报,人们可以计划对共同基金的长期投资。
- 它提供准确的估计。
- 根据您的财务需求,它通过提供参考您研究的共同基金过去表现的准确性来帮助塑造您的投资组合。
在开始实施之前,一些先决条件
- Android 中的 MVP (Model View Presenter) Architecture Pattern with Example – 通过示例了解 MVP 架构如何在 android 中工作。
- Android 中的 Material Design 组件滑块 – 了解如何使用 Android 中的滑块。
- 如何将饼图添加到 Android 应用程序中 - 了解如何在 android 中实现饼图。
在Android中实现SIP计算器的步骤
第 1 步:创建一个新项目
要在 Android Studio 中创建新项目,请参阅如何在 Android Studio 中创建/启动新项目。请注意,选择Kotlin作为编程语言。
步骤 2:使用 activity_main.xml 文件
应用程序的主要布局包含 3 个编辑文本和相关联的滑块,以便用户可以滑动和更改编辑文本而不是键入。一个计算按钮,按下后根据上述公式进行计算。一个是饼图,这个饼图有两个切片,一个是在该时期内的投资金额,另一个是在预期回报率和选定期限内产生的估计投资回报。要实现相同的调用,请在 activity_main.xml 文件中调用以下代码。
XML
XML
Kotlin
interface SipCalculatorModelInterface {
fun getTotalInvestedAmount(): Long
fun getEstimatedReturns(): Long
fun getTotalValue(): Long
}
Kotlin
interface SipCalculatorPresenterInterface {
fun forCalculation(
monthlyInvestmentAmount: String,
expectedReturnRate: String,
investmentTimePeriod: String
)
}
Kotlin
interface SipCalculatorViewInterface {
fun onCalculationResult(
totalInvestedAmount: String,
estimatedReturns: String,
totalValue: String
)
}
Kotlin
class SipCalculatorModel(
monthlyInvestmentAmount: String,
expectedReturnRate: String,
investmentTimePeriod: String
) : SipCalculatorModelInterface {
val TAG = SipCalculatorModel::class.java.simpleName
// convert all the inputs to integer.
private var monthlyInvestmentAmountInt: Int = monthlyInvestmentAmount.toInt()
private var expectedReturnRateInt: Int = expectedReturnRate.toInt()
private var investmentTimePeriodInt: Int = investmentTimePeriod.toInt() * 12
// total investment is considered here is according to monthly investment plans
override fun getTotalInvestedAmount(): Long {
return (monthlyInvestmentAmountInt * investmentTimePeriodInt).toLong()
}
// estimated returns = maturity value - total investment amount
override fun getEstimatedReturns(): Long {
return getTotalValue() - getTotalInvestedAmount()
}
// calculate the maturity value according to the formula
override fun getTotalValue(): Long {
val periodicInterest: Float = ((expectedReturnRateInt.toFloat() / 12) / 100)
return (monthlyInvestmentAmountInt * (((Math.pow(
(1 + periodicInterest).toDouble(),
investmentTimePeriodInt.toDouble()
)
- 1) / periodicInterest) * (1 + periodicInterest)))
.toLong()
}
}
Kotlin
import com.adityamshidlyali.gfgsipcalculator.Model.SipCalculatorModel
import com.adityamshidlyali.gfgsipcalculator.View.SipCalculatorViewInterface
class SipCalculatorPresenter(
private val sipCalculatorViewInterface: SipCalculatorViewInterface
) : SipCalculatorPresenterInterface {
override fun forCalculation(
monthlyInvestmentAmount: String,
expectedReturnRate: String,
investmentTimePeriod: String
) {
// create instance of the sip model and calculate all the results.
val sipModel = SipCalculatorModel(
monthlyInvestmentAmount,
expectedReturnRate,
investmentTimePeriod
)
// pass the data to view by accepting the context of the view class
sipCalculatorViewInterface.onCalculationResult(
sipModel.getTotalInvestedAmount().toString(),
sipModel.getEstimatedReturns().toString(),
sipModel.getTotalValue().toString()
)
}
}
Kotlin
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.adityamshidlyali.gfgsipcalculator.Presenter.SipCalculatorPresenter
import com.adityamshidlyali.gfgsipcalculator.R
import com.google.android.material.button.MaterialButton
import com.google.android.material.slider.Slider
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textview.MaterialTextView
import org.eazegraph.lib.charts.PieChart
import org.eazegraph.lib.models.PieModel
import java.text.NumberFormat
import java.util.*
class SipCalculatorView : AppCompatActivity(), SipCalculatorViewInterface {
// material text views
private lateinit var totalInvestedAmountMaterialTextView: MaterialTextView
private lateinit var estimatedReturnsMaterialTextView: MaterialTextView
private lateinit var totalAmountMaterialTextView: MaterialTextView
// material text input edit texts and text input layout
private lateinit var monthlyInvestmentAmountTextInputEditText: TextInputEditText
private lateinit var expectedReturnRateTextInputEditText: TextInputEditText
private lateinit var investmentTimePeriodTextInputEditText: TextInputEditText
private lateinit var monthlyInvestmentAmountTextInputLayout: TextInputLayout
private lateinit var expectedReturnRateTextInputLayout: TextInputLayout
private lateinit var investmentTimePeriodTextInputLayout: TextInputLayout
// buttons
private lateinit var sipCalculateResultButton: MaterialButton
// pie chart
private lateinit var sipResultPieChart: PieChart
// calculation result strings
private lateinit var monthlyInvestmentAmount: String
private lateinit var expectedReturnRate: String
private lateinit var investmentTimePeriod: String
// sliders
private lateinit var monthlyInvestmentAmountSlider: Slider
private lateinit var expectedReturnRateSlider: Slider
private lateinit var investmentTimePeriodSlider: Slider
@SuppressLint("UseCompatLoadingForDrawables", "SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// initialise all the UI elements
// material textViews
totalInvestedAmountMaterialTextView = findViewById(R.id.totalInvestedAmountMaterialTextView)
estimatedReturnsMaterialTextView = findViewById(R.id.estimatedReturnsMaterialTextView)
totalAmountMaterialTextView = findViewById(R.id.totalAmountMaterialTextView)
// material text input editTexts and text input layout
monthlyInvestmentAmountTextInputEditText =
findViewById(R.id.monthlyInvestmentAmountTextInputEditText)
expectedReturnRateTextInputEditText =
findViewById(R.id.expectedReturnRateTextInputEditText)
investmentTimePeriodTextInputEditText =
findViewById(R.id.investmentTimePeriodTextInputEditText)
monthlyInvestmentAmountTextInputLayout =
findViewById(R.id.monthlyInvestmentAmountTextInputLayout)
expectedReturnRateTextInputLayout =
findViewById(R.id.expectedReturnRateTextInputLayout)
investmentTimePeriodTextInputLayout =
findViewById(R.id.investmentTimePeriodTextInputLayout)
// buttons
sipCalculateResultButton = findViewById(R.id.sipCalculateResultButton)
// pie chart
sipResultPieChart = findViewById(R.id.sipResultPieChart)
// sliders
monthlyInvestmentAmountSlider = findViewById(R.id.monthlyInvestmentAmountSlider)
expectedReturnRateSlider = findViewById(R.id.expectedReturnRateSlider)
investmentTimePeriodSlider = findViewById(R.id.investmentTimePeriodSlider)
// setting initial values to all the UI elements
// TextInputEditTexts
monthlyInvestmentAmountTextInputEditText.setText("5000")
expectedReturnRateTextInputEditText.setText("14")
investmentTimePeriodTextInputEditText.setText("10")
// sliders
monthlyInvestmentAmountSlider.value = 5000f
expectedReturnRateSlider.value = 14f
investmentTimePeriodSlider.value = 10f
val sipPresenter = SipCalculatorPresenter(this)
sipPresenter.forCalculation(
"5000",
"14",
"10"
)
// handling all listeners for all the UI elements
// handling sliders
monthlyInvestmentAmountSlider.addOnChangeListener { slider, value, fromUser ->
monthlyInvestmentAmountTextInputEditText.setText(value.toInt().toString())
}
expectedReturnRateSlider.addOnChangeListener { slider, value, fromUser ->
expectedReturnRateTextInputEditText.setText(value.toInt().toString())
}
investmentTimePeriodSlider.addOnChangeListener { slider, value, fromUser ->
investmentTimePeriodTextInputEditText.setText(value.toInt().toString())
}
// handling buttons
sipCalculateResultButton.setOnClickListener {
monthlyInvestmentAmount = monthlyInvestmentAmountTextInputEditText.text.toString()
expectedReturnRate = expectedReturnRateTextInputEditText.text.toString()
investmentTimePeriod = investmentTimePeriodTextInputEditText.text.toString()
if (checkAllFields()) {
sipPresenter.forCalculation(
monthlyInvestmentAmount,
expectedReturnRate,
investmentTimePeriod
)
}
}
}
// update the text views in the
// result section of the view
// and also update the pie chart
override fun onCalculationResult(
totalInvestedAmount: String,
estimatedReturns: String,
totalValue: String
) {
val currencyFormatter: NumberFormat =
NumberFormat.getCurrencyInstance(Locale("en", "IN", "#"))
val totalInvestedAmountFormatted: String =
currencyFormatter.format(totalInvestedAmount.toLong())
val estimatedReturnsFormatted: String = currencyFormatter.format(estimatedReturns.toLong())
val totalValueFormatted: String = currencyFormatter.format(totalValue.toLong())
totalInvestedAmountMaterialTextView.text = totalInvestedAmountFormatted
estimatedReturnsMaterialTextView.text = estimatedReturnsFormatted
totalAmountMaterialTextView.text = totalValueFormatted
sipResultPieChart.clearAnimation()
sipResultPieChart.clearChart()
sipResultPieChart.addPieSlice(
PieModel(
"Invested Amount",
totalInvestedAmount.toFloat(),
ContextCompat.getColor(this, R.color.blue_200)
)
)
sipResultPieChart.addPieSlice(
PieModel(
"Estimated Returns",
totalValue.toFloat() - totalInvestedAmount.toFloat(),
ContextCompat.getColor(this, R.color.green_200)
)
)
sipResultPieChart.startAnimation()
}
// check whether all the text fields are filled or not
private fun checkAllFields(): Boolean {
if (monthlyInvestmentAmount.isEmpty()) {
monthlyInvestmentAmountTextInputEditText.error = "Can't be empty"
return false
}
if (expectedReturnRate.isEmpty()) {
expectedReturnRateTextInputEditText.error = "Can't be empty"
return false
}
if (investmentTimePeriod.isEmpty()) {
investmentTimePeriodTextInputEditText.error = "Can't be empty"
return false
}
return true
}
}
但是,以下是可选的,该应用程序带有 Google Material Design 建议的 Roboto 类型系统。在themes.xml文件中包含以下代码。
XML
第 3 步:使用架构
由于此应用程序所需的架构组件很小,但易于维护、记录和扩展应用程序。所以下图表示应该创建架构组件类和关联的接口,因为这里的接口在 MVP 架构中称为契约。为架构的每个组件创建一个包。
第 4 步:使用架构的所有接口(合同)
SipCalculatorModelInterface:
科特林
interface SipCalculatorModelInterface {
fun getTotalInvestedAmount(): Long
fun getEstimatedReturns(): Long
fun getTotalValue(): Long
}
SipCalculatorPresenter接口:
科特林
interface SipCalculatorPresenterInterface {
fun forCalculation(
monthlyInvestmentAmount: String,
expectedReturnRate: String,
investmentTimePeriod: String
)
}
SipCalculatorViewInterface:
科特林
interface SipCalculatorViewInterface {
fun onCalculationResult(
totalInvestedAmount: String,
estimatedReturns: String,
totalValue: String
)
}
第 5 步:使用 SipCalculatorModel.kt 类
此类实现 SipCalculatorModelInterface。这包含计算所有结果的主要逻辑。添加注释以便更好地理解。
科特林
class SipCalculatorModel(
monthlyInvestmentAmount: String,
expectedReturnRate: String,
investmentTimePeriod: String
) : SipCalculatorModelInterface {
val TAG = SipCalculatorModel::class.java.simpleName
// convert all the inputs to integer.
private var monthlyInvestmentAmountInt: Int = monthlyInvestmentAmount.toInt()
private var expectedReturnRateInt: Int = expectedReturnRate.toInt()
private var investmentTimePeriodInt: Int = investmentTimePeriod.toInt() * 12
// total investment is considered here is according to monthly investment plans
override fun getTotalInvestedAmount(): Long {
return (monthlyInvestmentAmountInt * investmentTimePeriodInt).toLong()
}
// estimated returns = maturity value - total investment amount
override fun getEstimatedReturns(): Long {
return getTotalValue() - getTotalInvestedAmount()
}
// calculate the maturity value according to the formula
override fun getTotalValue(): Long {
val periodicInterest: Float = ((expectedReturnRateInt.toFloat() / 12) / 100)
return (monthlyInvestmentAmountInt * (((Math.pow(
(1 + periodicInterest).toDouble(),
investmentTimePeriodInt.toDouble()
)
- 1) / periodicInterest) * (1 + periodicInterest)))
.toLong()
}
}
第 6 步:使用 SipCalculatorPresenter.kt 类
这个类实现了 SipCalculatorPresenterInterface,它只是在 SipCalculatorModel 和 SipCalculatorView 之间起作用。在 SipCalculatorPresenter.kt 类中调用以下代码。添加注释以便更好地理解。
科特林
import com.adityamshidlyali.gfgsipcalculator.Model.SipCalculatorModel
import com.adityamshidlyali.gfgsipcalculator.View.SipCalculatorViewInterface
class SipCalculatorPresenter(
private val sipCalculatorViewInterface: SipCalculatorViewInterface
) : SipCalculatorPresenterInterface {
override fun forCalculation(
monthlyInvestmentAmount: String,
expectedReturnRate: String,
investmentTimePeriod: String
) {
// create instance of the sip model and calculate all the results.
val sipModel = SipCalculatorModel(
monthlyInvestmentAmount,
expectedReturnRate,
investmentTimePeriod
)
// pass the data to view by accepting the context of the view class
sipCalculatorViewInterface.onCalculationResult(
sipModel.getTotalInvestedAmount().toString(),
sipModel.getEstimatedReturns().toString(),
sipModel.getTotalValue().toString()
)
}
}
第 7 步:使用 SipCalculatorView.kt 类
该类实现了 SipCalculatorViewInterface 并将该类的上下文提供给 Presenter 类以获取计算结果。它创建每个 UI 元素的实例,并在从演示者那里获得计算结果后更新 UI 元素。要实现相同的调用,请在 SipCalculatorView.kt 类中调用以下代码。添加注释以便更好地理解。
科特林
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.adityamshidlyali.gfgsipcalculator.Presenter.SipCalculatorPresenter
import com.adityamshidlyali.gfgsipcalculator.R
import com.google.android.material.button.MaterialButton
import com.google.android.material.slider.Slider
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textview.MaterialTextView
import org.eazegraph.lib.charts.PieChart
import org.eazegraph.lib.models.PieModel
import java.text.NumberFormat
import java.util.*
class SipCalculatorView : AppCompatActivity(), SipCalculatorViewInterface {
// material text views
private lateinit var totalInvestedAmountMaterialTextView: MaterialTextView
private lateinit var estimatedReturnsMaterialTextView: MaterialTextView
private lateinit var totalAmountMaterialTextView: MaterialTextView
// material text input edit texts and text input layout
private lateinit var monthlyInvestmentAmountTextInputEditText: TextInputEditText
private lateinit var expectedReturnRateTextInputEditText: TextInputEditText
private lateinit var investmentTimePeriodTextInputEditText: TextInputEditText
private lateinit var monthlyInvestmentAmountTextInputLayout: TextInputLayout
private lateinit var expectedReturnRateTextInputLayout: TextInputLayout
private lateinit var investmentTimePeriodTextInputLayout: TextInputLayout
// buttons
private lateinit var sipCalculateResultButton: MaterialButton
// pie chart
private lateinit var sipResultPieChart: PieChart
// calculation result strings
private lateinit var monthlyInvestmentAmount: String
private lateinit var expectedReturnRate: String
private lateinit var investmentTimePeriod: String
// sliders
private lateinit var monthlyInvestmentAmountSlider: Slider
private lateinit var expectedReturnRateSlider: Slider
private lateinit var investmentTimePeriodSlider: Slider
@SuppressLint("UseCompatLoadingForDrawables", "SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// initialise all the UI elements
// material textViews
totalInvestedAmountMaterialTextView = findViewById(R.id.totalInvestedAmountMaterialTextView)
estimatedReturnsMaterialTextView = findViewById(R.id.estimatedReturnsMaterialTextView)
totalAmountMaterialTextView = findViewById(R.id.totalAmountMaterialTextView)
// material text input editTexts and text input layout
monthlyInvestmentAmountTextInputEditText =
findViewById(R.id.monthlyInvestmentAmountTextInputEditText)
expectedReturnRateTextInputEditText =
findViewById(R.id.expectedReturnRateTextInputEditText)
investmentTimePeriodTextInputEditText =
findViewById(R.id.investmentTimePeriodTextInputEditText)
monthlyInvestmentAmountTextInputLayout =
findViewById(R.id.monthlyInvestmentAmountTextInputLayout)
expectedReturnRateTextInputLayout =
findViewById(R.id.expectedReturnRateTextInputLayout)
investmentTimePeriodTextInputLayout =
findViewById(R.id.investmentTimePeriodTextInputLayout)
// buttons
sipCalculateResultButton = findViewById(R.id.sipCalculateResultButton)
// pie chart
sipResultPieChart = findViewById(R.id.sipResultPieChart)
// sliders
monthlyInvestmentAmountSlider = findViewById(R.id.monthlyInvestmentAmountSlider)
expectedReturnRateSlider = findViewById(R.id.expectedReturnRateSlider)
investmentTimePeriodSlider = findViewById(R.id.investmentTimePeriodSlider)
// setting initial values to all the UI elements
// TextInputEditTexts
monthlyInvestmentAmountTextInputEditText.setText("5000")
expectedReturnRateTextInputEditText.setText("14")
investmentTimePeriodTextInputEditText.setText("10")
// sliders
monthlyInvestmentAmountSlider.value = 5000f
expectedReturnRateSlider.value = 14f
investmentTimePeriodSlider.value = 10f
val sipPresenter = SipCalculatorPresenter(this)
sipPresenter.forCalculation(
"5000",
"14",
"10"
)
// handling all listeners for all the UI elements
// handling sliders
monthlyInvestmentAmountSlider.addOnChangeListener { slider, value, fromUser ->
monthlyInvestmentAmountTextInputEditText.setText(value.toInt().toString())
}
expectedReturnRateSlider.addOnChangeListener { slider, value, fromUser ->
expectedReturnRateTextInputEditText.setText(value.toInt().toString())
}
investmentTimePeriodSlider.addOnChangeListener { slider, value, fromUser ->
investmentTimePeriodTextInputEditText.setText(value.toInt().toString())
}
// handling buttons
sipCalculateResultButton.setOnClickListener {
monthlyInvestmentAmount = monthlyInvestmentAmountTextInputEditText.text.toString()
expectedReturnRate = expectedReturnRateTextInputEditText.text.toString()
investmentTimePeriod = investmentTimePeriodTextInputEditText.text.toString()
if (checkAllFields()) {
sipPresenter.forCalculation(
monthlyInvestmentAmount,
expectedReturnRate,
investmentTimePeriod
)
}
}
}
// update the text views in the
// result section of the view
// and also update the pie chart
override fun onCalculationResult(
totalInvestedAmount: String,
estimatedReturns: String,
totalValue: String
) {
val currencyFormatter: NumberFormat =
NumberFormat.getCurrencyInstance(Locale("en", "IN", "#"))
val totalInvestedAmountFormatted: String =
currencyFormatter.format(totalInvestedAmount.toLong())
val estimatedReturnsFormatted: String = currencyFormatter.format(estimatedReturns.toLong())
val totalValueFormatted: String = currencyFormatter.format(totalValue.toLong())
totalInvestedAmountMaterialTextView.text = totalInvestedAmountFormatted
estimatedReturnsMaterialTextView.text = estimatedReturnsFormatted
totalAmountMaterialTextView.text = totalValueFormatted
sipResultPieChart.clearAnimation()
sipResultPieChart.clearChart()
sipResultPieChart.addPieSlice(
PieModel(
"Invested Amount",
totalInvestedAmount.toFloat(),
ContextCompat.getColor(this, R.color.blue_200)
)
)
sipResultPieChart.addPieSlice(
PieModel(
"Estimated Returns",
totalValue.toFloat() - totalInvestedAmount.toFloat(),
ContextCompat.getColor(this, R.color.green_200)
)
)
sipResultPieChart.startAnimation()
}
// check whether all the text fields are filled or not
private fun checkAllFields(): Boolean {
if (monthlyInvestmentAmount.isEmpty()) {
monthlyInvestmentAmountTextInputEditText.error = "Can't be empty"
return false
}
if (expectedReturnRate.isEmpty()) {
expectedReturnRateTextInputEditText.error = "Can't be empty"
return false
}
if (investmentTimePeriod.isEmpty()) {
investmentTimePeriodTextInputEditText.error = "Can't be empty"
return false
}
return true
}
}
输出: