I want to add some kotlin code on my flutter app. I add onCreate function on project_name/android/app/src/main/kotlin/com/example/app/MainActivity.kt.
This is my full MainActivity.kt
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import android.os.Environment
import android.net.Uri
import android.content.Intent
class MainActivity: FlutterActivity() {
private val channel = "externalStorage";
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel).setMethodCallHandler { call, result ->
when (call.method) {
"getExternalStorageDirectory" ->
result.success(Environment.getExternalStorageDirectory().toString())
"getExternalStoragePublicDirectory" -> {
val type = call.argument<String>("type")
result.success(Environment.getExternalStoragePublicDirectory(type).toString())
}
else -> result.notImplemented()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!Settings.canDrawOverlays(getApplicationContext())) {
val myIntent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
val uri: Uri = Uri.fromParts("package", getPackageName(), null)
myIntent.setData(uri)
startActivityForResult(myIntent, REQUEST_OVERLAY_PERMISSIONS)
return
}
}
}
then I got this error on my build
'onCreate' overrides nothing
Unresolved reference: Settings
Unresolved reference: Settings
Unresolved reference:
REQUEST_OVERLAY_PERMISSIONS
Do I need to import some code?
Finally, I found the answer. I need to import some code to add onCreate method.
import android.os.Bundle for Bundle parameter. See this link.
import android.provider.Settings because I use Settings.ACTION_MANAGE_OVERLAY_PERMISSION. See this link.
I forgot to add var REQUEST_OVERLAY_PERMISSIONS = 100
And now it works.
Related
I am having some issues with Firebase auth. I'm building an app using using Kotlin but keep retrieving the error 'W/System: Ignoring header X-Firebase-Locale because its value was null.'
I had this working previously, when I had set up the application using activities. I've now moved towards MVP architecture, but this seems to have broken my firebase authentication. I have also ensured that Email/Password sign in method has been enabled in the Firebase console.
If anyone could please take a look and hopefully you can see where I am going wrong. Many thanks.
SignInView
package org.wit.hikingtrails.views.signIn
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import org.wit.hikingtrails.databinding.ActivitySignInBinding
import com.google.firebase.auth.FirebaseAuth
import org.wit.hikingtrails.activities.SignUpActivity
import org.wit.hikingtrails.views.hikeList.HikeListView
import org.wit.hikingtrails.views.signUp.SignUpView
class SignInView : AppCompatActivity() {
lateinit var presenter: SignInPresenter
private lateinit var binding: ActivitySignInBinding
private lateinit var firebaseAuth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
presenter = SignInPresenter( this)
super.onCreate(savedInstanceState)
binding = ActivitySignInBinding.inflate(layoutInflater)
setContentView(binding.root)
firebaseAuth = FirebaseAuth.getInstance()
binding.textView.setOnClickListener {
val intent = Intent(this, SignUpView::class.java)
startActivity(intent)
}
binding.button.setOnClickListener {
val email = binding.emailEt.text.toString()
val pass = binding.passET.text.toString()
if (email.isNotEmpty() && pass.isNotEmpty()) {
presenter.doLogin(email, pass)
} else {
Toast.makeText(this, "Empty Fields Are not Allowed !!", Toast.LENGTH_SHORT).show()
}
}
}
override fun onStart() {
super.onStart()
if(firebaseAuth.currentUser != null){
val intent = Intent(this, HikeListView::class.java)
startActivity(intent)
}
}
}
SignInPresenter:
package org.wit.hikingtrails.views.signIn
import android.content.ContentValues.TAG
import android.content.Intent
import android.util.Log
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import com.google.firebase.auth.FirebaseAuth
import org.wit.hikingtrails.views.hikeList.HikeListView
import timber.log.Timber
import timber.log.Timber.i
class SignInPresenter (val view: SignInView) {
private lateinit var loginIntentLauncher : ActivityResultLauncher<Intent>
init{
registerLoginCallback()
}
var auth: FirebaseAuth = FirebaseAuth.getInstance()
fun doLogin(email: String, pass: String) {
// view.showProgress()
auth.signInWithEmailAndPassword(email, pass).addOnCompleteListener(view) { task ->
if (task.isSuccessful) {
val launcherIntent = Intent(view, HikeListView::class.java)
loginIntentLauncher.launch(launcherIntent)
} else {
i("Login failed:")
// val launcherIntent = Intent(view, HikeListView::class.java)
// loginIntentLauncher.launch(launcherIntent)
}
// view.hideProgress()
}
}
private fun registerLoginCallback(){
loginIntentLauncher =
view.registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{ }
}
}
For anyone that was having the same issue: The issue was with my API key. In kotlin, to debug, I used Timber.i( "signInWithCredential:failure ${task.exception?.message}") within the else statement of doLogin() in the presenter
i'm practising a bit with kotlin and was testing Room and livedata, my app gets data from a json and the stores it in room, i want to move this network call to its own file and class, but if i do so the observer i set to get the changes don't trigger anymore, any help would be appreciated
here is a snipped of my mainactivity, if more is needed to know what happens please let me know
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import androidx.room.Room
import com.optiva.videoplayer.data.*
import com.optiva.videoplayer.network.GetData
import com.optiva.videoplayer.network.Networking
import com.optiva.videoplayer.network.RetrofitConnect
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbcategories = Room.databaseBuilder(applicationContext, CategoriesDatabase::class.java,"categories.db").build()
val dbvideo = Room.databaseBuilder(applicationContext, VideosDatabase::class.java,"videos.db").build()
val retrofitData = RetrofitConnect.retrofitInst?.create(GetData::class.java)
val categoriesList = retrofitData?.getAll()
categoriesList?.enqueue(object: Callback<DataList> {
override fun onResponse(
call: Call<DataList>,
response: Response<DataList>
) {
val test = response?.body()
val cat = test?.categories
val vid = test?.videos
lifecycleScope.launch(Dispatchers.IO) {
if (cat != null) {
for(c in cat){
dbcategories.categoriesDAO().insertAll(CategoriesEntity(c.id,c.title,c.type))
}
}
if (vid != null) {
for(v in vid){
dbvideo.VideosDAO().insertAll(VideosEntity(v.id,v.thumb,v.videoUrl,v.categoryId,v.name))
}
}
}
}
override fun onFailure(call: Call<DataList>, t: Throwable) {
Toast.makeText(applicationContext,"error", Toast.LENGTH_LONG).show()
}
})
val textView: TextView = findViewById(R.id.test) as TextView
dbcategories.categoriesDAO().getALL().observeForever({categories ->
if(categories.size>0){
textView.text= categories[0].title
}
})
dbcategories.categoriesDAO().getALL().observe(this, {categories ->
if(categories.size>0){
textView.text= categories[0].title
}
}
} ```
I am trying to awake flutter method on a received call. I already done the receiving part, but I can't awake flutter method yet.
I tried calling method channel in onReceive() method in MainActivity.kt class, but it gives me error. method channel only seems to work in onCreate() method.
The question is how can I invoke flutter method in onReceive() or is there another way to do it?
MainActivity.kt
import android.Manifest
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.app.FlutterActivity
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity(){
var updateUIReciver: BroadcastReceiver? = null
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
registerReceiver(broadcastReceiver, IntentFilter("Service.to.activity"));
val channel = "my.data"
val methodChannel = MethodChannel(flutterView, channel)
val map: HashMap<String, String> = HashMap()
val permissionCheck: Int = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Permission granted ", Toast.LENGTH_LONG).show();
} else {
//TODO
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), 4);
Toast.makeText(this, "Permission not granted ", Toast.LENGTH_LONG).show();
}
methodChannel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result? ->
if (call.method == "callMyFunction") {
methodChannel.invokeMethod("callMyFunction", map)
} else {
}
}
}
var broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "Incoming call received", Toast.LENGTH_LONG).show()
// I can't call "methodChannel.invokeMethod("callMyFunction", map)" here cause of error.
}
}
}
MyBroadcastReceiver.kt
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.telephony.PhoneStateListener
import android.telephony.TelephonyManager
import androidx.core.app.NotificationCompat
import android.app.NotificationManager;
import android.os.Build;
import android.os.IBinder;
import android.widget.Toast
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val telephony = context.getSystemService(Service.TELEPHONY_SERVICE) as TelephonyManager
telephony.listen(object : PhoneStateListener() {
override fun onCallStateChanged(state: Int, incomingNumber: String) {
super.onCallStateChanged(state, incomingNumber)
context.sendBroadcast(Intent("Service.to.activity"))
}
}, PhoneStateListener.LISTEN_CALL_STATE)
}
}
Flutter code
const platform = const MethodChannel('my.data');
Future<void> _receiveFromNative(MethodCall call) async {
try {
if (call.method == "callMyFunction") {
print("Received in flutter");
}
} on PlatformException catch (e) {}
}
platform.setMethodCallHandler(_receiveFromNative);
Basically you can't access methodChannel in BroadcastReceiver directly so you have to make methodChannel in compaion object So,
Add these lines to your MainActivity
companion object {
lateinit var methodChannel: MethodChannel
}
And in onCreate method of MainActivity replace
val methodChannel = MethodChannel(flutterView, channel)
To :
methodChannel = MethodChannel(flutterView, channel)
now you can use MainActivity.methodChannel anywhere in your app.
Running into an Issue early on in importing a declared extension function in another Kotlin File (Extensions.kt), call the extension function From another class (ForecastsRepository.kt) it doesn't compile but when i remove it there is no problem with the build. Obviously I need it and wonder why importing it would become an issue .
Here is the class:
import com.benmohammad.climatemvvm.base.Success
import com.benmohammad.climatemvvm.custom.errors.ErrorHandler
import com.benmohammad.climatemvvm.custom.errors.NoDataException
import com.benmohammad.climatemvvm.custom.errors.NoResponseException
import com.benmohammad.climatemvvm.entitymappers.forecasts.ForecastMapper
import com.benmohammad.climatemvvm.features.home.di.HomeScope
import com.benmohammad.climatemvvm.network.api.OpenWeatherApi
import com.benmohammad.climatemvvm.network.response.ErrorResponse
import com.benmohammad.climatemvvm.room.dao.forecasts.ForecastDao
import com.benmohammad.climatemvvm.room.dao.utils.StringKeyValueDao
import com.benmohammad.climatemvvm.room.models.forecasts.DbForecast
import com.benmohammad.climatemvvm.utils.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import javax.inject.Inject
import com.benmohammad.climatemvvm.extensions.applyCommonSideEffects//import
#HomeScope
class ForecastsRepository #Inject constructor(
private val openWeatherApi: OpenWeatherApi,
private val forecastDao: ForecastDao,
private val stringKeyValueDao: StringKeyValueDao
) {
private val forecastCacheThresholdMillis = 3 * 3600000L //3 hours//
fun getForecasts(cityId: Int) = flow {
stringKeyValueDao.get(Utils.LAST_FORECASTS_API_CALL_TIMESTAMP)
?.takeIf { !Utils.shouldCallApi(it.value, forecastCacheThresholdMillis) }
?.let { emit(getDataOrError(NoDataException())) }
?: emit((getForecastFromAPI(cityId)))
}
//.applyCommonSideEffects()
.catch {
emit(getDataOrError(it))
}
private suspend fun getForecastFromAPI(cityId: Int) = openWeatherApi.getWeatherForecast(cityId)
.run {
if (isSuccessful && body() != null) {
stringKeyValueDao.insert(
Utils.getCurrentTimeKeyValuePair(Utils.LAST_FORECASTS_API_CALL_TIMESTAMP)
)
forecastDao.deleteAllAndInsert(ForecastMapper(body()!!).map())
getDataOrError(NoDataException())
} else {
Error(
NoResponseException(
ErrorHandler.parseError<ErrorResponse>(errorBody())?.message
)
)
}
}
private suspend fun getDataOrError(throwable: Throwable) =
forecastDao.get()
?.let { dbValue -> Success(getForecastList(dbValue)) }
?: Error(throwable)
private suspend fun getForecastList(dbForecast: DbForecast) = withContext(Dispatchers.Default) {
dbForecast.list.map { it.forecast }
}
}
and here is the file for the Extension functions:
package com.benmohammad.climatemvvm.extensions
import com.benmohammad.climatemvvm.base.Progress
import com.benmohammad.climatemvvm.base.Result
import com.benmohammad.climatemvvm.utils.Utils
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.retryWhen
import okhttp3.Call
import okhttp3.OkHttpClient
import okhttp3.Request
import retrofit2.Retrofit
import java.io.IOException
fun String.capitalizeWords(): String = this.split(' ').joinToString(" "){it.capitalize()}
#PublishedApi
internal inline fun Retrofit.Builder.callFactory(crossinline body: (Request) -> Call) =
callFactory(object: Call.Factory {
override fun newCall(request: Request): Call = body(request)
})
#Suppress("NOTHING_TO_INLINE")
inline fun Retrofit.Builder.delegatingCallFactory(delegate: dagger.Lazy<OkHttpClient>): Retrofit.Builder =
callFactory {
delegate.get().newCall(it) }
fun < T: Any> Flow<Result<T>>.applyCommonSideEffects() = //<<-----------T H I S Function!!!!HERE
retryWhen { cause, attempt ->
when {
(cause is IOException && attempt < Utils.MAX_RETRIES) -> {
delay(Utils.getBackOffDelay(attempt))
true
}
else -> {
false
}
}
}
.onStart { emit(Progress(isLoading = true)) }
.onCompletion { emit(Progress(isLoading = false)) }
fun Job?.cancelIfActive() {
if(this?.isActive == true) {
cancel()
}
}
as it doesnt compile it leads me to think the bug is deeper.
the IDE also hunderlines the function call stating it is "Unresolved reference"
GitHub Repo
Thanks any advice appreciated.
I'm trying to understand view models using kotlin for android, and I'm running into some difficulty. I have a very simple dummy app which allows a user to increment a number and then send that number to a second screen. That second screen will then display a random number between 0 and the sent number.
Here is the problem.
I understand how to send the data from the first page to the second using intents, and I know how to make a viewmodel in the second page. However, if I send the intent and then set the viewmodel equal to the set intent, it doesnt function properly. Rotating the screen will cause the intent to be resent and the viewmodel doesnt maintain the state of the data (the number rerandomizes).
Ideally, I'd like to just be able to just update the viewModel class in place of sending the intent, but the instance of the class is created when the second page is created, so that doesn't work.
Any ideas?
Based on the google codelabs "build my first android app" tutorial.
Here's my code; first page:
package com.example.patientplatypus.babbysfirstandroidapp
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.View
import android.view.Window
import android.view.WindowManager
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.textView
import org.jetbrains.anko.db.PRIMARY_KEY
import org.jetbrains.anko.db.UNIQUE
import org.jetbrains.anko.db.createTable
import android.database.sqlite.SQLiteDatabase
import android.support.v4.content.ContextCompat.startActivity
import com.example.patientplatypus.babbysfirstandroidapp.R.id.textView
import org.jetbrains.anko.db.*
import org.jetbrains.anko.indeterminateProgressDialog
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main);
}
fun toastMe(view: View) {
val myToast = Toast.makeText(this, "Hello Toast!", Toast.LENGTH_SHORT)
myToast.show()
}
fun countMe (view: View) {
Log.d("insideCountMeCheck", "hey you are inside count me!")
val countString = textView.text.toString()
var count: Int = Integer.parseInt(countString)
count++
textView.text = count.toString()
}
fun randomMe (view: View) {
val randomIntent = Intent(this, SecondActivity::class.java)
val countString = textView.text.toString()
val count = Integer.parseInt(countString)
randomIntent.putExtra(SecondActivity.TOTAL_COUNT, count.toString())
startActivity(randomIntent)
}
}
Here's my code, second page:
package com.example.patientplatypus.babbysfirstandroidapp
import android.arch.lifecycle.ViewModel
import android.arch.lifecycle.ViewModelProviders
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.Window
import android.view.WindowManager
import android.widget.Toast
import java.util.*
import kotlinx.android.synthetic.main.activity_second.randomText
class CountViewModel : ViewModel() {
var TOTAL_COUNT = "total_count"
}
class SecondActivity : AppCompatActivity() {
lateinit var countModel: CountViewModel
companion object {
const val TOTAL_COUNT = "total_count"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.setContentView(R.layout.activity_second)
countModel = ViewModelProviders.of(this).get(CountViewModel::class.java)
countModel.TOTAL_COUNT = intent.getStringExtra(TOTAL_COUNT)
displayForRandomNum(countModel.TOTAL_COUNT);
showRandomNumber()
}
fun showRandomNumber() {
val count = countModel.TOTAL_COUNT.toInt()
val random = Random()
var randomInt = 0
if (count > 0) {
randomInt = random.nextInt(count + 1)
}
Log.d("randomFinal", Integer.toString(randomInt))
displayForRandomNum(Integer.toString(randomInt))
}
fun displayForRandomNum(totalCount: String){
randomText.text = totalCount
}
}
An orientation change causes the activity be destroyed then recreated. This means onCreate will be called on every rotate. The same intent that started the activity originally would still be available to it. So intent.getStringExtra(TOTAL_COUNT) would return the original value from the intent that start the activity every time the screen is rotated. ViewModel will retain the data through the rotation though.
Your issue is that your overriding you're ViewModel'sTOTAL_COUNT with the original value from the intent every time. What you can do is check that the TOTAL_COUNT value isn't "total_count" (meaning its already been set from the intent) before setting it in onCreate.