flutterauthenticationhttpinterceptor

How to navigate from interceptor in flutter?


I am new to flutter.

I am using an interceptor to catch 401 http codes the API might respond. But I don't really understand how to make it go to login page when it happens. I wanted to use Navigator, but it requires a context which I don't have.

class AuthInterceptor implements InterceptorContract {
  @override
  Future<RequestData> interceptRequest({RequestData data}) async {
    print(data);
    return data;
  }

  @override
  Future<ResponseData> interceptResponse({ResponseData data}) async {
    if (data.statusCode == 401) {
      // What to do to go to login page?
    }
    return data;
  }
}

Can someone help me with it?


Solution

  • You can copy paste run full code below
    I use official weather example to simulate this case, because OPEN_WEATHER_API_KEY is invalid will return 401
    You can pass context to AuthInterceptor when init

    code snippet

    @override
      void initState() {
        repository = WeatherRepository(
          HttpClientWithInterceptor.build(interceptors: [
            AuthInterceptor(context: context),
          ]),
        );
    ...
    class AuthInterceptor implements InterceptorContract {
      final BuildContext context;
      AuthInterceptor({this.context});  
    
      @override
      Future<ResponseData> interceptResponse({ResponseData data}) async {
        print("status code ${data.statusCode}");
        if (data.statusCode == 401) {
          Future.delayed(Duration(seconds: 1), () {
            Navigator.pushNamedAndRemoveUntil(
                context, "/", (Route<dynamic> route) => false);
          });
        }
        return data;
      }
    }
    

    working demo

    enter image description here

    full code

    import 'dart:convert';
    import 'dart:io';
    
    import 'package:flutter/material.dart';
    import 'package:http_interceptor/http_interceptor.dart';
    
    const String OPEN_WEATHER_API_KEY = "YOUR-KEY-HERE";
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          initialRoute: '/',
          routes: {
            '/': (context) => Login(),
            '/home': (context) => HomeScreen(),
          },
        );
      }
    }
    
    class HomeScreen extends StatefulWidget {
      @override
      _HomeScreenState createState() => _HomeScreenState();
    }
    
    class _HomeScreenState extends State<HomeScreen> {
      WeatherRepository repository;
    
      @override
      void initState() {
        repository = WeatherRepository(
          HttpClientWithInterceptor.build(interceptors: [
            AuthInterceptor(context: context),
          ]),
        );
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            centerTitle: true,
            title: const Text('Weather App'),
            actions: <Widget>[
              IconButton(
                icon: Icon(Icons.search),
                onPressed: () {
                  showSearch(
                    context: context,
                    delegate: WeatherSearch(repository),
                  );
                },
              )
            ],
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Icon(
                  Icons.wb_sunny,
                  size: 64,
                  color: Colors.grey,
                ),
                Container(
                  height: 16,
                ),
                Text(
                  "Search for a city",
                  style: TextStyle(
                    fontSize: 24.0,
                    fontWeight: FontWeight.w300,
                  ),
                  textAlign: TextAlign.center,
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class WeatherSearch extends SearchDelegate<String> {
      int selected = -1;
      WeatherRepository repo;
    
      WeatherSearch(this.repo);
    
      @override
      List<Widget> buildActions(BuildContext context) {
        return [
          IconButton(
            icon: Icon(Icons.clear),
            onPressed: () {
              selected = -1;
              query = "";
            },
          )
        ];
      }
    
      @override
      Widget buildLeading(BuildContext context) {
        return IconButton(
          icon: AnimatedIcon(
            icon: AnimatedIcons.menu_arrow,
            progress: transitionAnimation,
          ),
          onPressed: () {
            close(context, null);
          },
        );
      }
    
      @override
      Widget buildResults(BuildContext context) {
        final city = selected == -1 ? null : cities[selected];
    
        return city != null ? buildWeatherCard(city) : buildEmptyCard();
      }
    
      @override
      Widget buildSuggestions(BuildContext context) {
        final suggestionList = query.isEmpty
            ? cities
            : cities.where((p) => p["name"].toString().startsWith(query)).toList();
        return ListView.builder(
          itemCount: suggestionList.length,
          itemBuilder: (context, index) {
            return ListTile(
              onTap: () {
                selected = index;
                query = cities[selected]["name"];
                showResults(context);
              },
              title: Text(suggestionList[index]['name']),
              subtitle: Text(suggestionList[index]['country']),
            );
          },
        );
      }
    
      Widget buildWeatherCard(final city) {
        return FutureBuilder(
          future: repo.fetchCityWeather(city["id"]),
          builder: (context, snapshot) {
            if (snapshot.hasError) {
              return Center(
                child: Text(snapshot.error),
              );
            }
    
            if (!snapshot.hasData) {
              return Center(
                child: CircularProgressIndicator(),
              );
            }
            final weather = snapshot.data;
            final iconWeather = weather["weather"][0]["icon"];
            final main = weather["main"];
            final wind = weather["wind"];
            return Card(
              margin: EdgeInsets.all(16.0),
              child: Container(
                width: Size.infinite.width,
                padding: EdgeInsets.all(16.0),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: <Widget>[
                    ListTile(
                      leading: Tooltip(
                        child: Image.network(
                            "https://openweathermap.org/img/w/$iconWeather.png"),
                        message: weather["weather"][0]["main"],
                      ),
                      title: Text(city["name"]),
                      subtitle: Text(city["country"]),
                    ),
                    ListTile(
                      title: Text("${main["temp"]} °C"),
                      subtitle: Text("Temperature"),
                    ),
                    ListTile(
                      title: Text("${main["temp_min"]} °C"),
                      subtitle: Text("Min Temperature"),
                    ),
                    ListTile(
                      title: Text("${main["temp_max"]} °C"),
                      subtitle: Text("Max Temperature"),
                    ),
                    ListTile(
                      title: Text("${main["humidity"]} %"),
                      subtitle: Text("Humidity"),
                    ),
                    ListTile(
                      title: Text("${main["pressure"]} hpa"),
                      subtitle: Text("Pressure"),
                    ),
                    ListTile(
                      title: Text("${wind["speed"]} m/s"),
                      subtitle: Text("Wind Speed"),
                    ),
                  ],
                ),
              ),
            );
          },
        );
      }
    
      Widget buildEmptyCard() {
        return Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Icon(
                Icons.wb_sunny,
                size: 64,
                color: Colors.grey,
              ),
              Container(
                height: 16,
              ),
              Text(
                "Search for a city",
                style: TextStyle(
                  fontSize: 24.0,
                  fontWeight: FontWeight.w300,
                ),
                textAlign: TextAlign.center,
              ),
            ],
          ),
        );
      }
    }
    
    const baseUrl = "https://api.openweathermap.org/data/2.5";
    
    class WeatherRepository {
      HttpClientWithInterceptor client;
    
      WeatherRepository(this.client);
    
      // Alternatively you can forget about using the Client and just doing the HTTP request with
      // the HttpWithInterceptor.build() call.
      // Future<Map<String, dynamic>> fetchCityWeather(int id) async {
      //   var parsedWeather;
      //   try {
      //     var response = await HttpWithInterceptor.build(
      //             interceptors: [WeatherApiInterceptor()])
      //         .get("$baseUrl/weather", params: {'id': "$id"});
      //     if (response.statusCode == 200) {
      //       parsedWeather = json.decode(response.body);
      //     } else {
      //       throw Exception("Error while fetching. \n ${response.body}");
      //     }
      //   } catch (e) {
      //     print(e);
      //   }
      //   return parsedWeather;
      // }
    
      Future<Map<String, dynamic>> fetchCityWeather(int id) async {
        var parsedWeather;
        try {
          final response =
              await client.get("$baseUrl/weather", params: {'id': "$id"});
          if (response.statusCode == 200) {
            parsedWeather = json.decode(response.body);
          } else {
            return Future.error(
              "Error while fetching.",
              StackTrace.fromString("${response.body}"),
            );
          }
        } on SocketException {
          return Future.error('No Internet connection 😑');
        } on FormatException {
          return Future.error('Bad response format 👎');
        } on Exception {
          return Future.error('Unexpected error 😢');
        }
    
        return parsedWeather;
      }
    }
    
    class AuthInterceptor implements InterceptorContract {
      final BuildContext context;
      AuthInterceptor({this.context});
    
      @override
      Future<RequestData> interceptRequest({RequestData data}) async {
        try {
          data.params['appid'] = OPEN_WEATHER_API_KEY;
          data.params['units'] = 'metric';
          data.headers[HttpHeaders.contentTypeHeader] = "application/json";
        } catch (e) {
          print(e);
        }
        print(data.params);
        return data;
      }
    
      @override
      Future<ResponseData> interceptResponse({ResponseData data}) async {
        print("status code ${data.statusCode}");
        if (data.statusCode == 401) {
          Future.delayed(Duration(seconds: 1), () {
            Navigator.pushNamedAndRemoveUntil(
                context, "/", (Route<dynamic> route) => false);
          });
        }
        return data;
      }
    }
    
    class Login extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: Text('Login Page')),
            body: Center(
                child: Column(
              children: [
                Text("Login Page"),
                RaisedButton(
                  child: Text('Login Success'),
                  onPressed: () {
                    Navigator.pushReplacementNamed(context, '/home');
                  },
                ),
              ],
            )));
      }
    }
    
    const cities = [
      {
        "id": 707860,
        "name": "Hurzuf",
        "country": "UA",
        "coord": {"lon": 34.283333, "lat": 44.549999}
      },
      {
        "id": 519188,
        "name": "Novinki",
        "country": "RU",
        "coord": {"lon": 37.666668, "lat": 55.683334}
      },
      {
        "id": 1283378,
        "name": "Gorkhā",
        "country": "NP",
        "coord": {"lon": 84.633331, "lat": 28}
      },
    ];