Skip to content

SoxiaLiSA/pixiv-login

Repository files navigation

pixiv-login

Android library for Pixiv OAuth 2.0 login (PKCE).
One dependency, three steps, done.

Setup

1. Add dependency

settings.gradle.kts

dependencyResolutionManagement {
    repositories {
        maven("https://jitpack.io")
    }
}

build.gradle.kts

dependencies {
    implementation("com.github.SoxiaLiSA:pixiv-login:1.2.0")
}

2. Register callback scheme in AndroidManifest.xml

This step is critical. Without it, the system cannot route the OAuth redirect back to your app.

Add an intent-filter to the Activity that will receive the login callback:

<activity
    android:name=".LoginActivity"
    android:launchMode="singleTask">

    <!-- Pixiv OAuth callback -->
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <!-- ⬇️ scheme must match the config you use ⬇️ -->
        <!-- PIXIV_ANDROID → "pixiv"      -->
        <!-- PIXIV_COMIC   → "pixiv-manga" -->
        <data android:scheme="pixiv" />
    </intent-filter>
</activity>
Config Scheme android:scheme
PixivOAuthConfig.PIXIV_ANDROID pixiv "pixiv"
PixivOAuthConfig.PIXIV_COMIC pixiv-manga "pixiv-manga"

Usage

Kotlin (3 steps)

// ① Create client (singleton, keep it alive)
val client = PixivOAuthClient(PixivOAuthConfig.PIXIV_ANDROID)

// ② Start login — open the URL in a browser
val url = client.startLogin()

// Option A: Chrome Custom Tab (recommended)
CustomTabsIntent.Builder().build().launchUrl(context, url.toUri())

// Option B: WebView
webView.loadUrl(url)

// ⚠️ WebView cannot use Google/Apple/third-party login
//    (Google blocks OAuth from WebView). Use Chrome Custom Tab
//    if your users need to log in via Google.

// ③ Handle callback — in your Activity
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    handleIntent(intent)
}

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    handleIntent(intent)
}

private fun handleIntent(intent: Intent?) {
    val result = client.tryHandleCallback(intent) ?: return
    when (result) {
        is PixivOAuthResult.Success -> {
            val accessToken  = result.response.accessToken
            val refreshToken = result.response.refreshToken
            val rawBody      = result.rawBody // raw JSON for custom deserialization
            // save tokens, navigate to main screen
        }
        is PixivOAuthResult.Failure -> {
            // Structured error handling — no string matching needed
            when (result) {
                is PixivOAuthResult.Failure.MissingVerifier -> {
                    // Process died between startLogin() and callback
                    Log.w("Login", "Verifier lost, please log in again")
                }
                is PixivOAuthResult.Failure.ServerRejected -> {
                    Log.e("Login", "Server rejected: HTTP ${result.httpCode}")
                }
                is PixivOAuthResult.Failure.NetworkError -> {
                    Log.e("Login", "Network error", result.cause)
                }
                is PixivOAuthResult.Failure.MissingCode -> {
                    Log.e("Login", "No code in callback URI")
                }
            }
        }
    }
}

tryHandleCallback does blocking I/O. Call it on a background thread or use the suspend version:

lifecycleScope.launch {
    val result = client.tryHandleCallbackSuspend(intent) ?: return@launch
    result.onSuccess { save(it.response.accessToken, it.response.refreshToken) }
           .onFailure { Log.e("Login", it.message) }
}

Refresh token

// The old refresh token is invalidated — always save the new one
val result = client.refreshToken(savedRefreshToken)
// or
val result = client.refreshTokenSuspend(savedRefreshToken)

Check expiry

if (response.isExpired()) {
    // token expired, call refreshToken
}

// Refresh 60 seconds early to avoid edge cases
if (response.isExpired(marginMillis = 60_000)) {
    // almost expired
}

Chrome Custom Tab vs WebView

Chrome Custom Tab WebView
Google / Apple login ❌ Google blocks OAuth in WebView
Share browser cookies
UI customization Limited Full control
Requires Chrome Yes No

Recommendation: Use Chrome Custom Tab. Pixiv supports Google login — if you use WebView, users who log in via Google will see an error page. Only use WebView if you are certain your users will only use Pixiv account + password.


Advanced

Survive process death

The default in-memory verifier is lost if Android kills your app during login. To fix this, implement VerifierStore:

class MmkvVerifierStore(private val mmkv: MMKV) : VerifierStore {
    override fun save(verifier: String) = mmkv.encode("pkce_verifier", verifier)
    override fun load(): String? = mmkv.decodeString("pkce_verifier")
    override fun clear() = mmkv.removeValueForKey("pkce_verifier")
}

val client = PixivOAuthClient(
    config = PixivOAuthConfig.PIXIV_ANDROID,
    verifierStore = MmkvVerifierStore(mmkv),
)

Disable built-in Pixiv headers

By default, the client adds User-Agent, App-OS, X-Client-Time, and X-Client-Hash headers. If you supply your own header interceptor via baseClient, opt out:

val client = PixivOAuthClient(
    config = PixivOAuthConfig.PIXIV_ANDROID,
    baseClient = yourOkHttpClient,
    addDefaultHeaders = false,
)

Debug HTTP logging

val client = PixivOAuthClient(
    config = PixivOAuthConfig.PIXIV_ANDROID,
    logHttp = true, // DO NOT enable in production
)

Share your app's OkHttpClient

val client = PixivOAuthClient(
    config = PixivOAuthConfig.PIXIV_ANDROID,
    baseClient = yourOkHttpClient, // inherits your DNS, Chucker, etc.
)

API at a glance

Method Description
startLogin() Generate PKCE + return login URL
tryHandleCallback(intent) Exchange code for tokens (if intent is a callback)
refreshToken(token) Get new access token
isExpired() / isExpired(margin) Check token expiry
*Suspend variants Cancellation-aware coroutine versions

Requirements

  • minSdk 21
  • Kotlin or Java

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages