Building Weather App using MVVM design pattern (Kotlin)
Contains
View show User Current Weather Report and next day Weather Report.
class MainActivity:AppCompatActivity() { |
|
val TAG: String? = MainActivity::class.java.simpleName
|
|
var access_key: String = BuildConfig.access_key /*"fc7ca27cdb74949f62f5b936e50db2c3"*/
|
|
var query: String = "Lucknow,Uttar Pradesh,IN"
|
|
var isUp = false
|
|
// Check Internet Continuosly
|
|
var TYPE_WIFI = 1
|
|
var TYPE_MOBILE = 2
|
|
var TYPE_NOT_CONNECTED = 0
|
|
var snackbar: Snackbar? = null
|
|
val coordinatorLayout: CoordinatorLayout? = null
|
|
var internetConnected = true
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
setContentView(R.layout.activity_main)
|
|
//create a current date
string.
|
|
val date_n = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(Date())
|
|
//set current date.
|
|
tv_current_date.text = date_n
|
|
//set current location.
|
|
var currentCity = query.split(",")
|
|
tv_current_location.text = currentCity[0]
|
|
if(checkConnectivity(applicationContext))
|
|
{
|
|
//Fetch Current Weather and
Next Day Forecasting repository data
|
|
getWeatherReport()
|
|
}else
|
|
{
|
|
val builder = AlertDialog.Builder(this@MainActivity)
|
|
//set title for alert dialog
|
|
builder.setTitle("Network Issues / Internet Connection Lost")
|
|
//set message for alert dialog
|
|
builder.setMessage("Turn on Network Data")
|
|
builder.setIcon(R.drawable.ic_no_internet)
|
|
//performing positive action
|
|
builder.setPositiveButton("Retry") { dialogInterface, which ->
|
|
if(checkConnectivity(applicationContext))
|
|
{
|
|
//Fetch
Current Weather and Next Day Forecasting repository data
|
|
getWeatherReport()
|
|
}
|
|
}
|
|
//performing negative action
|
|
builder.setNegativeButton("Exit") { dialogInterface, which ->
|
|
finish()
|
|
}
|
|
// Create the AlertDialog and
set Properties
|
|
val alertDialog: AlertDialog = builder.create()
|
|
alertDialog.setCancelable(false)
|
|
alertDialog.show()
|
|
}
|
|
//declaring swipe refresh
listener
|
|
swipeRefresh.setOnRefreshListener {
|
|
if(checkConnectivity(applicationContext))
|
|
{
|
|
//Fetch Current Weather and Next Day Forecasting repository
data
|
|
getWeatherReport()
|
|
}else
|
|
{
|
|
val builder = AlertDialog.Builder(this@MainActivity)
|
|
//set title for alert dialog
|
|
builder.setTitle("Network Issues / Internet Connection Lost")
|
|
//set message for alert dialog
|
|
builder.setMessage("Turn on Network Data")
|
|
builder.setIcon(R.drawable.ic_no_internet)
|
|
//performing positive action
|
|
builder.setPositiveButton("Retry") { dialogInterface, which ->
|
|
if(checkConnectivity(applicationContext))
|
|
{
|
|
//Fetch Current Weather and Next Day Forecasting repository
data
|
|
getWeatherReport()
|
|
}
|
|
}
|
|
//performing negative action
|
|
builder.setNegativeButton("Exit") { dialogInterface, which ->
|
|
finish()
|
|
}
|
|
// Create the AlertDialog and set Properties
|
|
val alertDialog: AlertDialog = builder.create()
|
|
alertDialog.setCancelable(false)
|
|
alertDialog.show()
|
|
}
|
|
}
|
|
}
|
|
private fun getWeatherReport() {
|
|
val weatherRepository = ViewModelProvider(this).get(WeatherRepository::class.java)
|
|
GetCurrentWeather(weatherRepository)
|
|
NextDayWeather(weatherRepository)
|
|
}
|
|
fun GetCurrentWeather(weatherRepository: WeatherRepository) {
|
|
swipeRefresh.isRefreshing = true
|
|
weatherRepository.getCurrentWeatherReport(access_key, query).observe(this, object : Observer<WeatherReport> {
|
|
override fun
onChanged(weatherReport: WeatherReport?) {
|
|
if (weatherReport == null) {
|
|
swipeRefresh.isRefreshing
= false
|
|
val builder = AlertDialog.Builder(this@MainActivity)
|
|
//set title for alert dialog
|
|
builder.setTitle(R.string.dialogTitle)
|
|
//set message for alert dialog
|
|
builder.setMessage(R.string.dialogMessage)
|
|
builder.setIcon(R.drawable.ic_error)
|
|
//performing positive action
|
|
builder.setPositiveButton("Retry") { dialogInterface, which ->
|
|
GetCurrentWeather(weatherRepository)
|
|
}
|
|
//performing negative action
|
|
builder.setNegativeButton("Exit") { dialogInterface, which ->
|
|
finish()
|
|
}
|
|
// Create the AlertDialog and set Properties
|
|
val alertDialog: AlertDialog = builder.create()
|
|
alertDialog.setCancelable(false)
|
|
alertDialog.show()
|
|
} else {
|
|
val currentTempKelvin = weatherReport?.main?.temp
|
|
val currentTempCelsius = currentTempKelvin?.minus(273.15)
|
|
//set current temperature.
|
|
tv_current_temperature.text
= currentTempCelsius?.toInt().toString()
|
|
//
tvStatus.setText(weatherReport.getWeather().get(0).getDescription())
|
|
tvHumidity.text = "" + weatherReport?.main?.humidity + " %"
|
|
tvSensible.text =
|
|
"" + (weatherReport?.main?.feels_like?.minus(273.15))?.toInt().toString() + Html.fromHtml(" \u2103")
|
|
tvPressure.text = "" + weatherReport?.main?.pressure + " hPa"
|
|
tvWind.text = "" + weatherReport?.wind?.speed + " km/h"
|
|
if (weatherReport != null) {
|
|
//set current weather description.
|
|
tv_current_description.text = weatherReport.weather?.get(0)?.description
|
|
//set current weather description image.
|
|
Picasso.get()
|
|
.load("http://openweathermap.org/img/wn/" + weatherReport.weather?.get(0)?.icon + "@2x.png")
|
|
.into(iv_current_description)
|
|
tv_today_temp_minmax.text =
|
|
weatherReport?.main?.tempMax?.minus(273.15)?.toInt().toString() + " / " + weatherReport?.main?.tempMin?.minus(
|
|
273.15
|
|
)?.toInt().toString()
|
|
val formatter = SimpleDateFormat("HH:mm:ss a ", Locale.US)
|
|
formatter.timeZone = TimeZone.getDefault()/*
TimeZone.getTimeZone("UTC")*/
|
|
/* val text =
formatter.format(Date((weatherReport.sys?.sunset)!!*1000))
|
|
println(text)*/
|
|
tv_sunrise.text = formatter.format(Date((weatherReport.sys?.sunrise)!! * 1000))
|
|
tv_sunset.text = formatter.format(Date((weatherReport.sys?.sunset)!! * 1000))
|
|
swipeRefresh.isRefreshing = false
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
fun NextDayWeather(weatherRepository: WeatherRepository) {
|
|
swipeRefresh.isRefreshing = true
|
|
weatherRepository.getForecastWeatherReport(access_key, query)
|
|
.observe(this, object : Observer<WeatherNextDaysReport> {
|
|
override fun
onChanged(weatherReport: WeatherNextDaysReport?) {
|
|
if (weatherReport == null) {
|
|
swipeRefresh.isRefreshing = false
|
|
val builder = AlertDialog.Builder(this@MainActivity)
|
|
//set title for alert dialog
|
|
builder.setTitle(R.string.dialogTitle)
|
|
//set message for alert dialog
|
|
builder.setMessage(R.string.dialogMessage)
|
|
builder.setIcon(R.drawable.ic_error)
|
|
//performing positive action
|
|
builder.setPositiveButton("Retry") { dialogInterface, which ->
|
|
NextDayWeather(weatherRepository)
|
|
}
|
|
//performing negative action
|
|
builder.setNegativeButton("Exit") { dialogInterface, which ->
|
|
finish()
|
|
}
|
|
// Create the AlertDialog and set Properties
|
|
val alertDialog: AlertDialog = builder.create()
|
|
alertDialog.setCancelable(false)
|
|
alertDialog.show()
|
|
} else {
|
|
val stringListDataHashMap = HashMap<String, ListData>()
|
|
if (weatherReport.list.isEmpty()) {
|
|
swipeRefresh.isRefreshing = false
|
|
} else {
|
|
//
|
|
for (y in 0 until weatherReport.list.size) {
|
|
stringListDataHashMap[weatherReport.list.get(y).getDtTxt()!!] =
|
|
weatherReport.list.get(y)
|
|
// System.out.println("Value
= " + weatherReport.getList().get(y).getDtTxt());
|
|
}
|
|
val listData = ArrayList<ListData>()
|
|
for (value in stringListDataHashMap.values)
{
|
|
System.out.println("Ascending before Value = " + value.getDtTxt())
|
|
listData.add(value)
|
|
}
|
|
// Sort date in assending order
|
|
Collections.sort(listData, object : Comparator<ListData> {
|
|
@SuppressLint("SimpleDateFormat")
|
|
var f: DateFormat = SimpleDateFormat("yyyy-MM-dd")
|
|
override fun compare(lhs: ListData, rhs: ListData): Int {
|
|
try {
|
|
return f.parse(lhs.getDtTxt()).compareTo(f.parse(rhs.getDtTxt()))
|
|
} catch (e: ParseException) {
|
|
e.printStackTrace()
|
|
throw IllegalArgumentException(e)
|
|
}
|
|
}
|
|
})
|
|
for (i in listData.indices) {
|
|
System.out.println("Ascending after Value = " + listData[i].getDtTxt())
|
|
}
|
|
rvNextDays.adapter = WeatherNextAdapter(applicationContext, listData)
|
|
rvNextDays.layoutManager = LinearLayoutManager(this@MainActivity)
|
|
swipeRefresh.isRefreshing = false
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
class WeatherNextAdapter(var context: Context, var listDataList: List<ListData>) :
|
|
RecyclerView.Adapter<WeatherNextAdapter.WeatherView>() {
|
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = WeatherView(
|
|
LayoutInflater.from(parent.context).inflate(R.layout.item_next_forecast, parent, false)
|
|
)
|
|
override fun onBindViewHolder(holder: WeatherView, position: Int) {
|
|
val listData = listDataList[position]
|
|
if (position != 0) {
|
|
Picasso.get()
|
|
.load("http://openweathermap.org/img/wn/" + listData.weather.get(0).icon + "@2x.png")
|
|
.into(holder.ivWeatherStatus)
|
|
if (position == 1) {
|
|
holder.tvDate.text = context.getString(R.string.tomorrow)
|
|
} else {
|
|
holder.tvDate.setText(DateConvert(listData.getDtTxt()!!))
|
|
}
|
|
holder.tvDescription.text = listData.weather[0].description
|
|
holder.tvDayName.setText(DayConvert(listData.getDtTxt()!!))
|
|
holder.tvTemp.text =
|
|
String.format(
|
|
"%d%s",
|
|
(listData.main?.temp?.minus(273.15))?.toInt(),
|
|
Html.fromHtml(" \u2103")
|
|
)
|
|
} else {
|
|
holder.itemView.visibility = View.GONE
|
|
}
|
|
}
|
|
override fun getItemCount(): Int {
|
|
return listDataList.size
|
|
}
|
|
class WeatherView(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
|
val tvDate: TextView
|
|
val tvDayName: TextView
|
|
val tvTemp: TextView
|
|
val tvDescription: TextView
|
|
val ivWeatherStatus: ImageView
|
|
init {
|
|
println("hittingh")
|
|
tvDate = itemView.findViewById(R.id.tvDate)
|
|
tvDayName = itemView.findViewById(R.id.tvDayName)
|
|
ivWeatherStatus = itemView.findViewById(R.id.ivWeatherStatus)
|
|
tvTemp = itemView.findViewById(R.id.tvTemp)
|
|
tvDescription = itemView.findViewById(R.id.tvDescription)
|
|
}
|
|
}
|
|
//convert date format in dd-MM
|
|
@SuppressLint("SimpleDateFormat")
|
|
fun DateConvert(date: String): String {
|
|
var dateString = date
|
|
try {
|
|
val sdf2 = SimpleDateFormat("dd-MM")
|
|
val sdf = SimpleDateFormat("yyyy-MM-dd")
|
|
dateString = sdf2.format(sdf.parse(dateString))
|
|
} catch (e: ParseException) {
|
|
e.printStackTrace()
|
|
}
|
|
return dateString
|
|
}
|
|
//convert date format in day
name
|
|
@SuppressLint("SimpleDateFormat")
|
|
fun DayConvert(dateString: String): String? {
|
|
val sdf = SimpleDateFormat("yyyy-MM-dd")
|
|
val sdf_ = SimpleDateFormat("EEEE")
|
|
var dayName: String? = null
|
|
try {
|
|
dayName = sdf_.format(sdf.parse(dateString))
|
|
} catch (e: ParseException) {
|
|
e.printStackTrace()
|
|
}
|
|
return dayName
|
|
}
|
|
}
|
|
// slide the view from below
itself to the current position
|
|
fun slideUp(view: View) {
|
|
view.visibility = View.VISIBLE
|
|
val animate = TranslateAnimation(
|
|
0f, // fromXDelta
|
|
0f, // toXDelta
|
|
view.height.toFloat(), // fromYDelta
|
|
0f
|
|
) // toYDelta
|
|
animate.duration = 500
|
|
animate.fillAfter = true
|
|
view.startAnimation(animate)
|
|
}
|
|
// slide the view from its
current position to below itself
|
|
fun slideDown(view: View) {
|
|
val animate = TranslateAnimation(
|
|
0f, // fromXDelta
|
|
0f, // toXDelta
|
|
0f, // fromYDelta
|
|
view.height.toFloat()
|
|
) // toYDelta
|
|
animate.duration = 500
|
|
animate.fillAfter = true
|
|
view.startAnimation(animate)
|
|
Handler().postDelayed({
|
|
view.visibility = View.GONE
|
|
}, 501)
|
|
}
|
|
fun onSlideViewButtonClick(view: View) {
|
|
if (isUp) {
|
|
slideDown(lay_nextdays)
|
|
iv_bottom_slide_up.setImageResource(R.drawable.ic_slideup)
|
|
} else {
|
|
slideUp(lay_nextdays)
|
|
iv_bottom_slide_up.setImageResource(R.drawable.ic_slidedown)
|
|
}
|
|
isUp = !isUp
|
|
}
|
|
/**
|
|
* Method to
register runtime broadcast receiver to show snackbar alert for internet
connection..
|
|
*/
|
|
private fun registerInternetCheckReceiver() {
|
|
val internetFilter = IntentFilter()
|
|
internetFilter.addAction("android.net.wifi.STATE_CHANGE")
|
|
internetFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE")
|
|
registerReceiver(broadcastReceiver,
internetFilter)
|
|
}
|
|
/**
|
|
* Runtime Broadcast
receiver inner class to capture internet connectivity events
|
|
*/
|
|
var broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
|
override fun onReceive(context: Context, intent: Intent) {
|
|
val status = getConnectivityStatusString(context)
|
|
setSnackbarMessage(status, false)
|
|
}
|
|
}
|
|
fun getConnectivityStatus(context: Context): Int {
|
|
val cm = context
|
|
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
|
val activeNetwork = cm.activeNetworkInfo
|
|
if (null != activeNetwork) {
|
|
if (activeNetwork.type == ConnectivityManager.TYPE_WIFI)
|
|
return TYPE_WIFI
|
|
if (activeNetwork.type == ConnectivityManager.TYPE_MOBILE)
|
|
return TYPE_MOBILE
|
|
}
|
|
return TYPE_NOT_CONNECTED
|
|
}
|
|
fun getConnectivityStatusString(context: Context): String? {
|
|
val conn = getConnectivityStatus(context)
|
|
var status: String? = null
|
|
if (conn == TYPE_WIFI) {
|
|
status = "Wifi enabled"
|
|
} else if (conn == TYPE_MOBILE) {
|
|
status = "Mobile data enabled"
|
|
} else if (conn == TYPE_NOT_CONNECTED) {
|
|
status = "Not connected to Internet"
|
|
}
|
|
return status
|
|
}
|
|
private fun setSnackbarMessage(status: String?, showBar: Boolean) {
|
|
var internetStatus = ""
|
|
try {
|
|
if (status!!.equals("Wifi enabled", ignoreCase = true) || status.equals(
|
|
"Mobile data enabled",
|
|
ignoreCase = true
|
|
)
|
|
) {
|
|
internetStatus = "Internet Connected"
|
|
window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
|
|
snackbar =
|
|
Snackbar.make(findViewById(android.R.id.content), internetStatus, Snackbar.LENGTH_LONG)
|
|
} else {
|
|
window.setFlags(
|
|
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
|
|
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
|
|
)
|
|
internetStatus = "Lost Internet Connection"
|
|
snackbar = Snackbar.make(
|
|
findViewById(android.R.id.content),
|
|
internetStatus,
|
|
Snackbar.LENGTH_INDEFINITE
|
|
)
|
|
}
|
|
val sbView = snackbar!!.getView()
|
|
val textView = sbView.findViewById(R.id.snackbar_text) as TextView
|
|
textView.setTextColor(Color.WHITE)
|
|
if (internetStatus.equals("Lost Internet Connection", ignoreCase = true)) {
|
|
if (internetConnected) {
|
|
snackbar!!.show()
|
|
internetConnected = false
|
|
}
|
|
} else {
|
|
if (!internetConnected) {
|
|
internetConnected = true
|
|
snackbar!!.show()
|
|
}
|
|
}
|
|
} catch (e: Exception) {
|
|
print(e)
|
|
}
|
|
}
|
|
override fun onPause() {
|
|
super.onPause()
|
|
unregisterReceiver(broadcastReceiver)
|
|
}
|
|
override fun onResume() {
|
|
super.onResume()
|
|
registerInternetCheckReceiver()
|
|
}
|
|
//Check internet connectivity
|
|
fun checkConnectivity(context: Context): Boolean {
|
|
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
|
val activeNetwork: NetworkInfo? =
connectivityManager.activeNetworkInfo
|
|
if (activeNetwork?.isConnected != null) {
|
|
return activeNetwork.isConnected
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
WeatherRepository.kt
Contains
ViewModel send data to view class of Current Weather Report and Next day Weather
Report and fetch repository data from model Class using Interface of retrofit
network Class.
class WeatherRepository : ViewModel() { |
|
val TAG: String?=WeatherRepository::class.java.simpleName
|
|
private val apiRequest: APIInterface = RetrofitRequest.getRetrofitInstance().create(APIInterface::class.java)
|
|
fun getCurrentWeatherReport(access_key: String,query: String): LiveData<WeatherReport> {
|
|
Log.d(TAG, "onResponse response:: $access_key $query")
|
|
val data = MutableLiveData<WeatherReport>()
|
|
apiRequest.getCurrentWeatherReport(access_key,
query)
|
|
.enqueue(object : Callback<WeatherReport> {
|
|
override fun onResponse(call: Call<WeatherReport>, response: Response<WeatherReport>) {
|
|
// Log.d(TAG, "onResponse response:: ${response.isSuccessful}")
|
|
/* Log.d(TAG, "onResponse response:: ${Gson().toJson(response)}")
|
|
*/
|
|
if (response.body() != null) {
|
|
data.value=response.body()
|
|
// Log.d(TAG,
"onResponse response:: ${data.value?.main?.temp}")
|
|
}
|
|
}
|
|
override fun onFailure(call: Call<WeatherReport>, t: Throwable) {
|
|
//
data.value=null
|
|
}
|
|
})
|
|
return data
|
|
}
|
|
fun getForecastWeatherReport(access_key: String,query: String): LiveData<WeatherNextDaysReport> {
|
|
Log.d(TAG, "onResponse response:: $access_key $query")
|
|
val data = MutableLiveData<WeatherNextDaysReport>()
|
|
apiRequest.getForecastWeatherReport(access_key, query)
|
|
.enqueue(object : Callback<WeatherNextDaysReport> {
|
|
override fun onResponse(call: Call<WeatherNextDaysReport>, response: Response<WeatherNextDaysReport>) {
|
|
Log.d(TAG, "onResponse response:: ${Gson().toJson(response)}")
|
|
if (response.body() != null) {
|
|
data.value=response.body()
|
|
}
|
|
}
|
|
override fun onFailure(call: Call<WeatherNextDaysReport>, t: Throwable) {
|
|
// data.value=null
|
|
}
|
|
})
|
|
return data
|
|
}
|
|
}
Further More
Model Classes can be found on my github Account with better Explanation
|
Comments