1

I'm creating an app where the user has to insert a serverurl in an EditText field, and that url should be the baseUrl of the retrofit-request. So, my code works as it should when i use a hardcoded baseurl, but the app crashes when I try to pass the value from the Edittext to the baseUrl.

Thats how I tried to pass the value:

object NetworkLayer {

        var newUrl: String = ""

        val retrofit: Retrofit
            get() = Retrofit.Builder()
                .baseUrl(newUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build()

        val myApi: MyApi by lazy {
            retrofit.create(MyApi::class.java)
        }
        val apiClient = ApiClient(myApi)
    }

and in my MainActivity:

 var serverUrl = binding.et1.text.toString()
        
        button.setOnClickListener {
            NetworkLayer.newUrl = serverUrl
            viewModel.getServerInformation(headerValue)
        }

I get this error message: Error message: Caused by: java.lang.IllegalArgumentException: Expected URL scheme 'http' or 'https' but no scheme was found for.

So probably retrofit uses the empty "" string for the request. Somehow I should send the information to retrofit that when clicking the button the url from the Edittext (et1) is the baseUrl. When I use a seperate class (f.e. class Constants, with a companion object with a const val baseUrl = "hardcoded url") it works also. Can I create a function to inform the retrofit client to use the Edittext as baseUrl and declare it in the onClickListener? or could it be a way to create the retrofit client in a class instead of an object? (using url: String as parameter in the class and adding the edittext as argument in the MainActivity?) Sadly the @Url annotation for Retrofit doesn't work as I have to use also @Header and @Query in the different requests.

Or is there a compeletey different way for doing this?

Hopefully there is someone who can help me.

Alex Mutschl
  • 27
  • 1
  • 7
  • sounds like you should add `http://` or `https://` to the value entered by the user. Although https://stackoverflow.com/questions/32559333/retrofit-2-dynamic-url might give you some alternative ideas. – Chris Dec 19 '22 at 16:50
  • Hello, I tried the app with the emulator. I inserted the same url in the edittext, which I used as hard-coded. Hard-coded worked, when inserted in edittext not.. That's why I think that the value of edittext didn't get passed as baseurl. Thanks for the link, eventually I can use @Url and add the queries in another way. But I am not sure what to do with the headers.. Didn't tought that it is that hard to use the edittext as variable and pass it to baseurl... :-) – Alex Mutschl Dec 19 '22 at 18:16
  • Then you should check the value extracted from the Edit Text. If you're using kotlin a ` println` statement should be sufficient, or perhaps a breakpoint. – Chris Dec 20 '22 at 08:00
  • Debugging shows me "" , not the value I inserted the edittext-field. So the edittext is not "transferred" to the Networklayer - variable. Any hint why it's not working? – Alex Mutschl Dec 20 '22 at 12:53
  • It could be that `apiClient` is instantiated immediately. And at that point the url is the value of `newUrl`, which is the empty string. Perhaps change `apiClient` to be lazy also? – Chris Dec 20 '22 at 14:42
  • Oh that sounds comprehensible.. You mean val apiClient by lazy { ApiClient(myApi)} I am not sure if it works that way, I have to try it tomorrow. What about creating a fun as: fun getServerUrl() {var serverUrl = binding.et1.text.toString() NetworkLayer.newUrl = serverUrl} and call that function first in the onclicklistener: button.setOnClickListener { getServerUrl() viewModel.getServerInformation(headerValue) } Could this work also? – Alex Mutschl Dec 20 '22 at 18:03
  • I think there are better ways to implement your network layer. But you can work on that easier when you know you have something functional. So try the lazy idea first. Then you could have a look at https://developer.android.com/topic/architecture for how Google would recommend. Pay particular attention to the sample app links at the bottom of the page. – Chris Dec 20 '22 at 18:11
  • I get an error when trying to add by lazy for apiClient.. I'll read the article again (read it some time ago), maybe it helps :-) so the way of using MVVM with view-viewmodel-repository-model I undestand more or less - and I know how to make f.e. an api request and passing the response to a textview in the ui. My greatest difficult is the opposite direction. It's not clear for me how to pass a value from ui to viewmodel etc. to use it in a request or so. For a second value that is used as part of a Header on the requests I was able to do it by parameters starting from the api interface. – Alex Mutschl Dec 21 '22 at 12:17
  • @Chris I posted an answer on how I solved the problem. Maybe you can take a look on it, is it ok to use it that way? Thanks four your help!!! :-) – Alex Mutschl Dec 22 '22 at 13:12

1 Answers1

0

I managed to solve it, the only thing I had to change was: val url = binding.etServerUrl.text instead of val url = binding.etServerUrl.text.toString()

and when calling the function on button click I added the toString() to the url argument. When I try to add the toString() to the val url as I always did before it doesn't work, anyone can tell me why?

Here is an example how I use it (I changed the Retrofit client a bit to my first version in the question). So finally I can go ahead with my app, as I was blocked now for a few weeks with this.. :-)

object RetrofitClient{

    var retrofitService: MyApi? = null
    
    fun getInstance(url: String): MyApi{

        if (retrofitService == null) {
            val retrofit = Retrofit.Builder()
                .baseUrl(url)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
            retrofitService = retrofit.create(MyApi::class.java)
        }
        return retrofitService!!
    }
}

I changed the retrofitclient a bit, but it wors Then in the repository:

class MainRepository (){

    suspend fun getToken(cookie: String, url: String): TokenResponse? {
        val request = RetrofitClient.getInstance(url).getToken(cookie)

        if (request?.isSuccessful!!) {
            return request.body()!!
        }

        return null
    }
}

Viewmodel:

class SharedViewModel() : ViewModel() {

    private val repository = MainRepository()


    private val _getTokenLiveData = MutableLiveData<TokenResponse>()
    val getTokenLiveData: LiveData<TokenResponse> = _getTokenLiveData


    fun getToken(cookie: String, url: String) {
        viewModelScope.launch {
            val response = repository.getToken(cookie, url)
            _getTokenLiveData.postValue(response)
        }
    }
}

And finally the MainActivity:

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    val viewModel: SharedViewModel by lazy {
        ViewModelProvider(this)[SharedViewModel::class.java]
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater) //initializing the binding class
        setContentView(binding.root)

        val url = binding.etServerUrl.text
        val headerValue = binding.etMac.text.toString()
        val button = binding.button
        val textView = binding.textView

        button.setOnClickListener {
            viewModel.getToken(headerValue, url = url.toString())
        }


        viewModel.getTokenLiveData.observe(this) { response ->
            if (response == null) {
                Toast.makeText(this@MainActivity, "Fehlerhaft", Toast.LENGTH_SHORT).show()
                return@observe
            }
            textView.text = response.js.token

        }

    }
}
Alex Mutschl
  • 27
  • 1
  • 7