square / retrofit

A type-safe HTTP client for Android and the JVM
https://square.github.io/retrofit/
Apache License 2.0
43.11k stars 7.3k forks source link

Strange "java.net.MalformedURLException: no protocol: " error #2856

Closed Aqluse closed 6 years ago

Aqluse commented 6 years ago

Sorry, don't familiar with tests yet, so just information about issue I keep getting. Error message is always as it is in title.

val monstercatRetrofit by lazy {
    Retrofit.Builder().baseUrl("https://connect.monstercat.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
}

val monstercatAPI = monstercatRetrofit.create(MonstercatAPI::class.java)

interface MonstercatAPI {
    @POST("signin") // Goes well
    fun signIn(@Body identity: Login): Call<Gson>

    @GET("api/self") // Goes well with cookie
    fun self(@Header("cookie") cookie: String): Call<AccountInfo> // Cookie is required for this call

    @GET("api/catalog/browse") // Goes well until converting to data class, but it's my problem
    fun tracks(
            @Query("search") search: String? = null,
            @Query("playlistId") playlistId: String? = null,
            @Query("albumId") albumId: String? = null,
            @Query("isrc") isrc: String? = null,
            @Query("types") types: List<String> = emptyList(),
            @Query("genre") genre: List<String> = emptyList(),
            @Query("tags") tags: List<String> = emptyList()
    ): Call<BrowseTracksResults>

@GET("api/catalog/release") // Goes with error, no any values passed, so parameters can be erased (tried)
    fun releases(
            @Query("search") search: String? = null,
            @Query("playlistId") playlistId: String? = null,
            @Query("albumId") albumId: String? = null,
            @Query("isrc") isrc: String? = null,
            @Query("types") types: List<String> = emptyList(),
            @Query("genre") genre: List<String> = emptyList(),
            @Query("tags") tags: List<String> = emptyList()
    ): Call<BrowseReleasesResults>
}

Objects you might need:

data class BrowseReleasesResults(
        val sort: Sort,
        val total: Int = 0,
        val results: List<Release> = emptyList()
)

data class ReleaseTracksResults(
        val hiddenTracks: Int = 0,
        val total: Int = 0,
        val results: List<Track> = emptyList()
)

data class Track(
        @SerializedName("_id") val id: String,
        val title: String,
        val artistsTitle: String,
        val bpm: Double,  
        val artists: List<ArtistEntity> = emptyList(),
        val featuring: List<ArtistEntity> = emptyList(),
        val remixers: List<ArtistEntity> = emptyList(),
        val label: String,
        val albums: List<AlbumEntry> = emptyList(),
        val albumNames: List<String> = emptyList(),
        val albumCatalogIds: List<String> = emptyList(),
        val genres: List<String> = emptyList(),
        val genre: String = "", // because nullable
        /*val iswc: ?, //null*/
        val isrc: String,
        val duration: Double, 
        val licensable: Boolean,
        val tags: List<String> = emptyList(), // Descriptive attributes
        val created: Date,
        val catalogs: List<String> = emptyList(),
        val hasErrors: Boolean,
        val tagusId: String,
        /*val youTubeVideoId: List<?> = emptyList(),*/

        /*val disabledFor: ?, //null*/
        /*val releaseDate: ?, //null*/
        val release: Release?,  
        val deleted: Boolean?,
        val artistDetails: ArtistDetails?,
        val artistUsers: ArtistUsers?,
        val debutDate: Date?,

        val streamable: Boolean?,
        val downloadable: Boolean?,
        val inEarlyAccess: Boolean?,
        val freeDownloadForUsers: Boolean?
)

data class ArtistEntity(
        @SerializedName("_id") val id: String,
        val name: String,
        val artistId: String
)

data class AlbumEntry(
        @SerializedName("_id") val id: String,
        val trackNumber: Int,
        val albumId: String,
        val isFree: Boolean,
        val streamHash: String
)

data class Release(
        @SerializedName("_id") val id: String,
        val title: String,
        val renderedArtists: String = "",
        val catalogId: String,
        val type: String?, // Single, EP, etc.  //TODO: implement with enum
        val upc: String?,
        val grid: String?,
        val releaseDate: Date,
        val label: String?,
        val tags: List<String> = emptyList(), // Descriptive attributes
        val freeDownloadForUsers: Boolean, // Able to download separate tracks when log in account
        val showToAdminsOnly: Boolean?,
        val showOnWebsite: Boolean?,
        val showAsFree: Boolean, // Able to download separate tracks when log in account
        val urls: List<URLEntity> = emptyList(),
        val tagusId: String?,
        val coverUrl: URL?,

         val streamable: Boolean?,
        val downloadable: Boolean?,
        val inEarlyAccess: Boolean?,

        val thumbHashes: Map<String, String> = emptyMap(),
        val imageHashSum: String?,

         val coverArt: String?, // Not equal to coverUrl at all, not full URL

         val deleted: Boolean?,

         val preReleaseDate: Date?
)

data class URLEntity(
        @SerializedName("_id") val id: String?, // Not exists in "urls" of ArtistDetails
        val original: URL,
        val short: URL?,
        val platform: String
)

data class ArtistDetails(
        @SerializedName("_id") val id: String,
        val urls: List<URLEntity> = emptyList(),
        val years: List<Int> = emptyList(), 
        val names: String,
        val profileImageUrl: URL,
        val vanityUri: String,
        val public: Boolean,
        val shopifyCollectionId: String // But always empty or null
)

data class ArtistUsers(
        @SerializedName("_id") val id: String,
        val name: String,
        val websiteDetailsId: String
)

data class Sort(
        val releaseDate: Int //TODO: implement with enum

My usage of this:

monstercatAPI.releases().enqueue(object : Callback<BrowseReleasesResults> { 
                override fun onResponse(call: Call<BrowseReleasesResults>, response: Response<BrowseReleasesResults>) {
                    if (response.isSuccessful)
                        setItems(response.body()!!.results.map { it.toCollectionModel() })
                    else
                        Log.d("Retrofit failure", "${response.code()}")
                }

                override fun onFailure(call: Call<BrowseReleasesResults>, t: Throwable) { // Falling into there with MalformedURLException
                    TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
                }
            })

URL that supposed to be called (https://connect.monstercat.com/api/catalog/release/) is completely valid and works well in browser. I have completely no idea what could be wrong.

JakeWharton commented 6 years ago

Please include the entire stacktrace.

Aqluse commented 6 years ago

@JakeWharton

W/System.err: java.net.MalformedURLException: no protocol: 
W/System.err:     at java.net.URL.<init>(URL.java:589)
        at java.net.URL.<init>(URL.java:486)
        at java.net.URL.<init>(URL.java:435)
        at com.google.gson.internal.bind.TypeAdapters$21.read(TypeAdapters.java:491)
        at com.google.gson.internal.bind.TypeAdapters$21.read(TypeAdapters.java:483)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
        at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82)
        at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
        at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
        at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:82)
        at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
W/System.err:     at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
        at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:39)
        at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27)
        at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:122)
        at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:217)
        at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:116)
        at okhttp3.RealCall$AsyncCall.execute(RealCall.java:153)
        at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
        at java.lang.Thread.run(Thread.java:760)
JakeWharton commented 6 years ago

The stacktrace indicates that this exception occurs while Gson is parsing the response. This is not a problem with Retrofit. Your model likely contains a URL field and the JSON contains a value which does not have a scheme.