api_client_dio 0.1.0 copy "api_client_dio: ^0.1.0" to clipboard
api_client_dio: ^0.1.0 copied to clipboard

A simple, opinionated Dio wrapper for Flutter. Handles base URL, token injection, 401 refresh/redirect, logging, and timeouts out of the box. Framework-agnostic.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:api_client_dio/api_client_dio.dart';

/// Example TokenProvider using in-memory storage.
/// Replace with SharedPreferences / FlutterSecureStorage in real apps.
class InMemoryTokenProvider implements TokenProvider {
  String? _accessToken;
  String? _refreshToken;

  @override
  Future<String?> get accessToken async => _accessToken;

  @override
  Future<String?> get refreshToken async => _refreshToken;

  @override
  Future<void> onUnauthorized() async {
    _accessToken = null;
    _refreshToken = null;
    print('[Auth] Token expired — user needs to login');
  }

  @override
  Future<void> onTokenRefreshed({
    required String accessToken,
    String? refreshToken,
  }) async {
    _accessToken = accessToken;
    _refreshToken = refreshToken;
    print('[Auth] Token refreshed successfully');
  }

  void login(String token, {String? refresh}) {
    _accessToken = token;
    _refreshToken = refresh;
  }

  void logout() {
    _accessToken = null;
    _refreshToken = null;
  }
}

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late final InMemoryTokenProvider _tokenProvider;
  late final ApiClient _client;
  String _status = 'Not started';
  final _baseUrlController = TextEditingController(
    text: 'https://jsonplaceholder.typicode.com',
  );
  final _emailController = TextEditingController(text: 'eve.holt@reqres.in');
  final _passwordController = TextEditingController(text: 'cityslicka');

  @override
  void initState() {
    super.initState();
    _tokenProvider = InMemoryTokenProvider();
    _client = ApiClient(
      baseUrl: _baseUrlController.text,
      tokenProvider: _tokenProvider,
      enableLogging: true,
    );
  }

  @override
  void dispose() {
    _baseUrlController.dispose();
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'API Client Demo',
      theme: ThemeData(primarySwatch: Colors.teal),
      home: Scaffold(
        appBar: AppBar(title: const Text('API Client Demo')),
        body: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              TextField(
                controller: _baseUrlController,
                decoration: const InputDecoration(
                  labelText: 'Base URL',
                  border: OutlineInputBorder(),
                ),
              ),
              const SizedBox(height: 16),
              ElevatedButton.icon(
                onPressed: _testGet,
                icon: const Icon(Icons.download),
                label: const Text('GET /posts/1'),
              ),
              const SizedBox(height: 8),
              ElevatedButton.icon(
                onPressed: _testPost,
                icon: const Icon(Icons.upload),
                label: const Text('POST /posts'),
              ),
              const SizedBox(height: 16),
              const Text('Login Demo (reqres.in)',
                  style: TextStyle(fontWeight: FontWeight.bold)),
              const SizedBox(height: 8),
              TextField(
                controller: _emailController,
                decoration: const InputDecoration(
                  labelText: 'Email',
                  border: OutlineInputBorder(),
                ),
              ),
              const SizedBox(height: 8),
              TextField(
                controller: _passwordController,
                decoration: const InputDecoration(
                  labelText: 'Password',
                  border: OutlineInputBorder(),
                ),
                obscureText: true,
              ),
              const SizedBox(height: 8),
              ElevatedButton.icon(
                onPressed: _testLogin,
                icon: const Icon(Icons.login),
                label: const Text('POST /api/login'),
              ),
              const SizedBox(height: 16),
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.grey.shade100,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Text(
                  _status,
                  style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Future<void> _testGet() async {
    try {
      _setStatus('Loading...');
      final response = await _client.get('/posts/1');
      _setStatus('✅ GET /posts/1 → ${response.statusCode}\n'
          '${response.data}');
    } catch (e) {
      _setStatus('❌ Error: $e');
    }
  }

  Future<void> _testPost() async {
    try {
      _setStatus('Loading...');
      final response = await _client.post('/posts', data: {
        'title': 'API Client Dio',
        'body': 'Testing the package',
        'userId': 1,
      });
      _setStatus('✅ POST /posts → ${response.statusCode}\n'
          '${response.data}');
    } catch (e) {
      _setStatus('❌ Error: $e');
    }
  }

  Future<void> _testLogin() async {
    try {
      _client.updateBaseUrl('https://reqres.in');
      _setStatus('Logging in...');
      final response = await _client.post('/api/login', data: {
        'email': _emailController.text,
        'password': _passwordController.text,
      });
      final token = response.data['token'] as String?;
      if (token != null) {
        _tokenProvider.login(token);
        _setStatus('✅ Login success!\nToken: $token');
      } else {
        _setStatus('⚠️ No token in response:\n${response.data}');
      }
      _client.updateBaseUrl(_baseUrlController.text);
    } catch (e) {
      _setStatus('❌ Login error: $e');
      _client.updateBaseUrl(_baseUrlController.text);
    }
  }

  void _setStatus(String msg) {
    setState(() => _status = msg);
  }
}
0
likes
160
points
30
downloads

Documentation

API reference

Publisher

verified publisherciptaantaradigital.com

Weekly Downloads

A simple, opinionated Dio wrapper for Flutter. Handles base URL, token injection, 401 refresh/redirect, logging, and timeouts out of the box. Framework-agnostic.

Repository (GitHub)
View/report issues

Topics

#dio #api #http #network #interceptor

License

MIT (license)

Dependencies

dio, flutter

More

Packages that depend on api_client_dio